本文目录导读:

- 核心设计思路
- AWS 环境 (Python + Boto3)
- Azure 环境 (Bash + Azure CLI)
- 阿里云环境 (Python + Aliyun SDK)
- Kubernetes 环境 (清理 Service 或 Pod 残留子网)
- 风险控制与最佳实践
清理未使用的子网是云资源管理和成本优化中的重要任务,由于不同云平台(AWS、Azure、GCP、阿里云等)的API和资源模型差异较大,自动化脚本需要针对具体平台编写。
以下提供几种主流云平台的自动化脚本示例,以及通用的设计思路和风险控制措施。
核心设计思路
- 列举所有子网:通过云平台API列出当前区域/项目下的所有子网。
- 检测关联资源:检查子网是否被以下资源关联(这是最关键的过滤条件):
- 云主机/虚拟机:网卡(ENI)是否位于该子网。
- 负载均衡器:其可用区交换机是否指向该子网。
- 数据库服务(RDS, Redis, MongoDB等):是否部署在该子网。
- 容器服务(K8s, ECS Fargate, ACK):Pod或Service是否使用该子网。
- NAT网关、VPN连接、专线等网络设备。
- 弹性网卡/辅助网卡。
- 其他VPC对等连接/TR(中转路由器)的路由表条目。
- 标记与清理:
- 先标记:列出未使用子网清单,人工确认或发通知。
- 后删除:只有确认无依赖后,才调用API删除。强烈不建议直接删除。
AWS 环境 (Python + Boto3)
AWS 的 VPC 和子网管理非常成熟,核心是检查子网内是否有 EC2、ENI、RDS、ELB 等。
import boto3
from botocore.exceptions import ClientError
def list_unused_subnets(region='us-east-1'):
"""列出指定区域内所有未使用的子网"""
ec2_client = boto3.client('ec2', region_name=region)
ec2_resource = boto3.resource('ec2', region_name=region)
# 1. 获取所有子网
subnets = list(ec2_resource.subnets.all())
print(f"总子网数量: {len(subnets)}")
# 2. 获取所有正在使用的网卡 (ENI)
# 注意:ENI是判断子网是否被使用的核心,每个EC2、NAT、ELB等都会创建ENI
used_enis = set()
paginator = ec2_client.get_paginator('describe_network_interfaces')
for page in paginator.paginate():
for eni in page['NetworkInterfaces']:
if 'SubnetId' in eni:
used_enis.add(eni['SubnetId'])
# 3. 获取所有子网级别的路由表关联(排除主路由表,主表通常有默认路由)
# 逻辑:如果子网显式关联了路由表,且该表关联了其他资源如IGW、NAT等,也算在使用
used_subnets_in_routes = set()
route_tables = ec2_client.describe_route_tables()['RouteTables']
for rt in route_tables:
for assoc in rt['Associations']:
if 'SubnetId' in assoc:
used_subnets_in_routes.add(assoc['SubnetId'])
# 4. 检查其他服务(如RDS, ElastiCache, Redshift等)
# 可以通过DescribeDBSubnetGroups等API获取关联的子网
used_rds_subnets = set()
try:
rds_client = boto3.client('rds', region_name=region)
paginator = rds_client.get_paginator('describe_db_subnet_groups')
for page in paginator.paginate():
for group in page['DBSubnetGroups']:
for subnet in group['Subnets']:
used_rds_subnets.add(subnet['SubnetIdentifier'])
except ClientError:
pass # 没有权限或没有RDS资源
# 综合所有使用中的子网
all_used = used_enis.union(used_subnets_in_routes, used_rds_subnets)
# 5. 筛选未使用的子网
unused_subnets = []
for subnet in subnets:
if subnet.id not in all_used:
# 可选:进一步检查子网内的ENI数量,确保没有隐藏的对象
eni_in_subnet = ec2_client.describe_network_interfaces(
Filters=[{'Name': 'subnet-id', 'Values': [subnet.id]}]
)['NetworkInterfaces']
if len(eni_in_subnet) == 0:
unused_subnets.append({
'id': subnet.id,
'vpc_id': subnet.vpc_id,
'cidr': subnet.cidr_block,
'az': subnet.availability_zone
})
return unused_subnets
# 执行并生成报告
if __name__ == "__main__":
unused = list_unused_subnets()
if unused:
print(f"发现 {len(unused)} 个未使用子网:")
for subnet in unused:
print(f" - {subnet['id']} (VPC: {subnet['vpc_id']}, CIDR: {subnet['cidr']})")
# 安全删除(建议先导出人工确认)
# for subnet in unused:
# ec2 = boto3.resource('ec2')
# ec2.Subnet(subnet['id']).delete()
else:
print("没有找到未使用的子网。")
Azure 环境 (Bash + Azure CLI)
Azure 的 VNet 和子网检查逻辑类似,但资源模型略有不同。
#!/bin/bash
# 检查未使用的 Azure 子网
# 前提:已登录 az login
RG="your-resource-group" # 可选,指定资源组;不指定则检查所有
SUBSCRIPTION_ID=$(az account show --query id -o tsv)
echo "开始扫描子网..."
# 1. 获取所有子网 ID
if [ -z "$RG" ]; then
SUBNETS=$(az network vnet list --query "[].subnets[].id" -o tsv)
else
SUBNETS=$(az network vnet list -g $RG --query "[].subnets[].id" -o tsv)
fi
# 2. 获取所有与子网关联的网络接口 (NIC)
ALL_NICS=$(az network nic list --query "[].ipConfigurations[].subnet.id" -o tsv)
# 3. 获取负载均衡器、应用网关等关联的后端子网
ALL_LB_SUBNETS=$(az network lb list --query "[].frontendIPConfigurations[].subnet.id" -o tsv)
ALL_APPGW_SUBNETS=$(az network application-gateway list --query "[].gatewayIPConfigurations[].subnet.id" -o tsv)
# 合并所有被使用的子网(去重)
USED_SUBNETS=$(echo -e "$ALL_NICS\n$ALL_LB_SUBNETS\n$ALL_APPGW_SUBNETS" | sort -u)
# 4. 对比并输出未使用的子网
UNUSED_COUNT=0
for SUBNET_ID in $SUBNETS; do
SUB_NAME=$(echo $SUBNET_ID | awk -F'/' '{print $NF}')
VNET_NAME=$(echo $SUBNET_ID | awk -F'/' '{print $9}')
if ! echo "$USED_SUBNETS" | grep -Fxq "$SUBNET_ID"; then
# 可选:检查路由表关联,确保没有隐藏用途
RT_ASSOC=$(az network vnet subnet show --ids $SUBNET_ID --query "routeTable.id" -o tsv)
if [ -z "$RT_ASSOC" ]; then # 如果没关联路由表,则确认未使用
echo "未使用子网: $VNET_NAME -> $SUB_NAME ($SUBNET_ID)"
((UNUSED_COUNT++))
fi
fi
done
echo "总计 $UNUSED_COUNT 个未使用子网。"
# 如果要删除,可以添加:
# for SUBNET in ...; do
# az network vnet subnet delete --ids $SUBNET_ID
# done
阿里云环境 (Python + Aliyun SDK)
阿里云使用 VPC API,核心检查对象是 ReservedPublicAddress、NetworkInterface 和 VSwitch。
from aliyunsdkvpc.request.v20160428 import DescribeVSwitchesRequest, DeleteVSwitchRequest
from aliyunsdkecs.request.v20140526 import DescribeNetworkInterfacesRequest
import json
def get_unused_vswitches(region_id='cn-hangzhou'):
# 客户端初始化 (需配置AccessKey)
from aliyunsdkcore.client import AcsClient
client = AcsClient('your-access-key-id', 'your-access-key-secret', region_id)
# 1. 获取所有交换机
request = DescribeVSwitchesRequest.DescribeVSwitchesRequest()
request.set_PageSize(50)
response = client.do_action_with_exception(request)
vswitch_list = json.loads(response)['VSwitches']['VSwitch']
# 2. 获取所有弹性网卡 (ENI),并提取其所属的VSwitch ID
eni_request = DescribeNetworkInterfacesRequest.DescribeNetworkInterfacesRequest()
eni_request.set_PageSize(100)
eni_response = client.do_action_with_exception(eni_request)
eni_list = json.loads(eni_response)['NetworkInterfaceSets']['NetworkInterfaceSet']
# 收集已使用的VSwitch ID
used_vswitches = set()
for eni in eni_list:
if 'VSwitchId' in eni:
used_vswitches.add(eni['VSwitchId'])
# 3. 过滤出未使用的交换机
unused = []
for vsw in vswitch_list:
vsw_id = vsw['VSwitchId']
if vsw_id not in used_vswitches:
# 可选:检查是否有默认SNAT条目或其他依赖
# 略...
unused.append({
'id': vsw_id,
'vpc': vsw['VpcId'],
'cidr': vsw['CidrBlock'],
'zone': vsw['ZoneId']
})
return unused
if __name__ == "__main__":
unused = get_unused_vswitches()
for vsw in unused:
print(f"未使用交换机: {vsw['id']} (VPC: {vsw['vpc']}, CIDR: {vsw['cidr']})")
Kubernetes 环境 (清理 Service 或 Pod 残留子网)
如果是自建 K8s 集群(例如使用 Calico、Flannel)或云原生集群(AKS/EKS/ACK),子网可能被 Service 的 LoadBalancer 或 Pod 网络的 IP 占用,但 API 层面可能不显示。
# 检查 Kubernetes 集群中是否还有指向某子网的 Service # 查找所有 LoadBalancer 类型的 Service,以及其关联的 IP 段 kubectl get svc --all-namespaces | grep LoadBalancer # 检查 Ingress Controller 是否占用子网 kubectl get ingress --all-namespaces # 检查 NodePort 或 ClusterIP 通常不占用子网,但如果你使用了云厂商的 VPC-CNI 插件 # 需要检查 Pod 是否还在使用该子网的 IP 段 # 可以使用 calicoctl 或 cilium 的命令来检查 IP 分配情况
风险控制与最佳实践
- 安全第一:切勿直接删除,先导出未使用子网清单,发给所有相关团队(网络、安全、应用)确认。
- 设置保护锁:在云控制台或 Terraform/Pulumi 中为关键子网添加
PreventDestroy或禁止删除标签。 - 保留一定时间窗口:标记为“未使用”后,设置一个冷却期(如7天),期间该子网被再次使用则移出清理名单。
- 考虑CIDR冲突:删除子网可能会导致现有VPC的CIDR范围缩小,未来无法复用该IP段,可先在内部网络规划中记录。
- 自动化通知:将脚本集成到 CI/CD 或定时任务中,定期生成报告并发送到 Slack/邮件,由管理员手动批准删除。
- 检查非显式依赖:
- 保留路由规则:如果子网已在路由表中有显式路由(如指向NAT/IGW),可能预示着未来扩展。
- DHCP选项集:非常用,但可能有关联。
- DNS解析记录:子网的Private DNS Zone若有记录,可能被其他服务引用。
没有“万能一键清理脚本”,因为每个环境的拓扑、权限和业务依赖不同,推荐的做法是:
- 第一步:使用上述脚本扫描并生成未使用子网清单。
- 第二步:人工复核(联系网络/应用团队)。
- 第三步:设置自动清理任务,但仅对为
auto-delete=true或创建超过30天的空闲子网执行删除。
这样可以最大程度地避免“误删可用资源”导致的生产事故。