自动化脚本如何清理未使用子网

wen 实用脚本 1

本文目录导读:

自动化脚本如何清理未使用子网

  1. 核心设计思路
  2. AWS 环境 (Python + Boto3)
  3. Azure 环境 (Bash + Azure CLI)
  4. 阿里云环境 (Python + Aliyun SDK)
  5. Kubernetes 环境 (清理 Service 或 Pod 残留子网)
  6. 风险控制与最佳实践

清理未使用的子网是云资源管理和成本优化中的重要任务,由于不同云平台(AWS、Azure、GCP、阿里云等)的API和资源模型差异较大,自动化脚本需要针对具体平台编写。

以下提供几种主流云平台的自动化脚本示例,以及通用的设计思路和风险控制措施。

核心设计思路

  1. 列举所有子网:通过云平台API列出当前区域/项目下的所有子网。
  2. 检测关联资源:检查子网是否被以下资源关联(这是最关键的过滤条件):
    • 云主机/虚拟机:网卡(ENI)是否位于该子网。
    • 负载均衡器:其可用区交换机是否指向该子网。
    • 数据库服务(RDS, Redis, MongoDB等):是否部署在该子网。
    • 容器服务(K8s, ECS Fargate, ACK):Pod或Service是否使用该子网。
    • NAT网关、VPN连接、专线等网络设备。
    • 弹性网卡/辅助网卡
    • 其他VPC对等连接/TR(中转路由器)的路由表条目。
  3. 标记与清理
    • 先标记:列出未使用子网清单,人工确认或发通知。
    • 后删除:只有确认无依赖后,才调用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,核心检查对象是 ReservedPublicAddressNetworkInterfaceVSwitch

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 分配情况

风险控制与最佳实践

  1. 安全第一切勿直接删除,先导出未使用子网清单,发给所有相关团队(网络、安全、应用)确认。
  2. 设置保护锁:在云控制台或 Terraform/Pulumi 中为关键子网添加 PreventDestroy 或禁止删除标签。
  3. 保留一定时间窗口:标记为“未使用”后,设置一个冷却期(如7天),期间该子网被再次使用则移出清理名单。
  4. 考虑CIDR冲突:删除子网可能会导致现有VPC的CIDR范围缩小,未来无法复用该IP段,可先在内部网络规划中记录。
  5. 自动化通知:将脚本集成到 CI/CD 或定时任务中,定期生成报告并发送到 Slack/邮件,由管理员手动批准删除。
  6. 检查非显式依赖
    • 保留路由规则:如果子网已在路由表中有显式路由(如指向NAT/IGW),可能预示着未来扩展。
    • DHCP选项集:非常用,但可能有关联。
    • DNS解析记录:子网的Private DNS Zone若有记录,可能被其他服务引用。

没有“万能一键清理脚本”,因为每个环境的拓扑、权限和业务依赖不同,推荐的做法是:

  • 第一步:使用上述脚本扫描并生成未使用子网清单
  • 第二步:人工复核(联系网络/应用团队)。
  • 第三步:设置自动清理任务,但仅对为 auto-delete=true 或创建超过30天的空闲子网执行删除。

这样可以最大程度地避免“误删可用资源”导致的生产事故。

抱歉,评论功能暂时关闭!