AWS Global View 实战指南:多区域资源一览无余,从控制台到IaC全栈教程

如果你管理着跨多个区域的 AWS 基础设施(生产环境通常都这样),你一定体会过在控制台各个区域标签间来回跳转的痛:为了查一下全球有多少台 EC2 实例,或者找出遗忘在 ap-southeast-1 的孤儿弹性 IP,得挨个点开每个区域。AWS Global View 完美解决了这个问题。

AWS Global View 是内置于 EC2/VPC 控制台(位于 us-east-1)的一个只读、单窗格控制台,它可以在一个页面上汇总你账户下所有已启用区域的 EC2 和 VPC 资源。无需任何配置、零成本。但大多数 AWS 工程师并不知道它的存在,更别说如何用 CLI、Terraform、CDK 和 Boto3 来编程扩展它的能力了。

本文将从控制台深度剖析讲起,覆盖 CLI 脚本、Terraform/CDK 基础设施即代码,以及可直接投入生产的 Python 全局库存脚本,一次性讲透。

什么是 AWS Global View?

AWS Global View 是一个只读的多区域资源浏览器,嵌入在 AWS EC2 控制台中。它让你不用切换区域就能鸟瞰整个 EC2 和 VPC 舰队。它不能创建、修改或删除资源——纯粹是一个观察工具。

控制台访问地址:

https://<your-account-id>.us-east-1.console.aws.amazon.com/awsglobalview/home?region=us-east-1#RegionExplorer

小贴士: AWS Global View 只能在 us-east-1 作为主区域使用。从其他区域的控制台端点无法访问。同时,Firefox 隐私/无痕窗口也不支持。

该服务与 AWS Resource Explorer v2 紧密集成,后者提供了驱动全局搜索功能的底层索引和搜索引擎。

支持的资源

AWS Global View 目前支持以下资源(覆盖所有已启用的区域):

计算
– EC2 实例、Auto Scaling 组、容量预留、容量块

网络
– VPC、子网、互联网网关、仅出口互联网网关、NAT 网关、路由表、网络 ACL、网络接口、弹性 IP、安全组

连接
– VPC 端点、VPC 对等连接、端点服务、托管前缀列表

存储
– EBS 卷、S3 存储桶

数据库
– RDS 数据库实例、数据库集群

基础设施
– DHCP 选项集、Outposts、可用区

控制台深度解析

Global View 控制台有四个核心部分:

区域探索器

登录页。顶部显示摘要部分——所有已启用区域中每种资源类型的总数,且可点击。例如,如果显示“29 个实例,分布在 10 个区域”,点击后会列出全部 29 个及其所属区域。摘要下方是按区域细分的表格——每行一个区域,每列一种资源类型,显示数量。点击任一单元格即可深入查看该区域中该资源类型的详情。

全局搜索

搜索标签页允许你同时按区域资源类型标签进行过滤。这是 AWS 中原生跨区域资产库存最接近的功能。你可以点击任何资源 ID 直接跳转到其原生控制台(例如,点击实例 ID 会打开该区域对应的 EC2 控制台)。

区域和可用区

该标签页列出你账户下的所有 AWS 区域、可用区、本地区域和 Wavelength 区域。在这里可以启用/禁用区域以及选择加入本地区域——对于需要将基础设施限制在特定地理位置的合规场景至关重要。

设置

可以自定义控制台,隐藏你不使用的资源类型和区域。如果你的组织只使用 us-east-1ap-south-1eu-west-1,隐藏其他区域以减少干扰。

所需 IAM 权限

用户至少需要 ec2:Describe*rds:Describe* 读取权限才能访问 Global View。以下是最低权限策略:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "GlobalViewReadOnly",
      "Effect": "Allow",
      "Action": [
        "ec2:DescribeRegions",
        "ec2:DescribeInstances",
        "ec2:DescribeVpcs",
        "ec2:DescribeSubnets",
        "ec2:DescribeSecurityGroups",
        "ec2:DescribeVolumes",
        "ec2:DescribeNatGateways",
        "ec2:DescribeInternetGateways",
        "ec2:DescribeRouteTables",
        "ec2:DescribeNetworkAcls",
        "ec2:DescribeNetworkInterfaces",
        "ec2:DescribeAddresses",
        "ec2:DescribeAvailabilityZones",
        "ec2:DescribeVpcPeeringConnections",
        "ec2:DescribeVpcEndpoints",
        "ec2:DescribeManagedPrefixLists",
        "ec2:DescribeAutoScalingGroups",
        "rds:DescribeDBInstances",
        "rds:DescribeDBClusters",
        "s3:ListAllMyBuckets",
        "account:GetRegionOptStatus",
        "account:ListRegions"
      ],
      "Resource": "*"
    }
  ]
}

安全最佳实践: 将此策略附加给 IAM 角色,而非 IAM 用户。如果在 Lambda 函数或 GitHub Actions CI/CD 任务中使用,请通过 STS 进行角色代入——切勿硬编码访问密钥。

第一部分:AWS CLI —— 在终端中复现 Global View

AWS Global View 没有专用的 CLI 命令。你可以使用 ec2account CLI 子命令来模拟它的行为。以下所有示例均假设你已经安装了 AWS CLI v2 并配置了合适的凭据。

1.1 —— 列出所有已启用区域

# 列出所有已选择加入或默认启用的区域
aws ec2 describe-regions \
  --filters "Name=opt-in-status,Values=opt-in-not-required,opted-in" \
  --query "Regions[*].RegionName" \
  --output table

1.2 —— 按区域统计 EC2 实例数量

# 循环遍历每个区域,统计 EC2 实例数量
echo "Region | Instance Count" && echo "-------|---------------"
for region in $(aws ec2 describe-regions \
  --filters "Name=opt-in-status,Values=opt-in-not-required,opted-in" \
  --query "Regions[*].RegionName" --output text); do
  count=$(aws ec2 describe-instances \
    --region "$region" \
    --query "length(Reservations[*].Instances[])" \
    --output text 2>/dev/null || echo 0)
  echo "$region | $count"
done

1.3 —— 列出所有全局 VPC 及其 CIDR 块

# 遍历所有区域,输出每个区域的 VPC 信息
for region in $(aws ec2 describe-regions \
  --query "Regions[*].RegionName" --output text); do
  echo "=== Region: $region ==="
  aws ec2 describe-vpcs \
    --region "$region" \
    --query "Vpcs[*].{VpcId:VpcId,CIDR:CidrBlock,Default:IsDefault,State:State}" \
    --output table 2>/dev/null
done

1.4 —— 查找所有未挂载的 EBS 卷(FinOps 场景)

孤立的 EBS 卷是 AWS 成本浪费的最大来源之一。以下脚本可在全局范围内发现它们:

# 全局扫描处于“可用”状态的 EBS 卷(即未挂载)
echo "Hunting orphaned volumes across all regions..."
for region in $(aws ec2 describe-regions \
  --query "Regions[*].RegionName" --output text); do
  result=$(aws ec2 describe-volumes \
    --region "$region" \
    --filters "Name=status,Values=available" \
    --query "Volumes[*].{Region:'$region',VolumeId:VolumeId,Size:Size,Type:VolumeType}" \
    --output table 2>/dev/null)
  if [[ -n "$result" && "$result" != *"None"* ]]; then
    echo "=== $region ==="
    echo "$result"
  fi
done

1.5 —— 查找对外开放的安全组(0.0.0.0/0 的 22 或 3389 端口)

这是一个全局安全审计,通常需要手动逐区域进行:

# 扫描所有区域,查找 SSH(22)端口开放给 0.0.0.0/0 的安全组
for region in $(aws ec2 describe-regions \
  --query "Regions[*].RegionName" --output text); do
  aws ec2 describe-security-groups \
    --region "$region" \
    --filters "Name=ip-permission.from-port,Values=22" \
             "Name=ip-permission.cidr,Values=0.0.0.0/0" \
    --query "SecurityGroups[*].{Region:'$region',GroupId:GroupId,GroupName:GroupName,VpcId:VpcId}" \
    --output table 2>/dev/null
done

1.6 —— 通过 CLI 启用/禁用区域

# 检查某个区域的选择加入状态(必须针对 us-east-1 端点)
aws account get-region-opt-status \
  --region-name ap-east-1 \
  --region us-east-1

# 启用一个区域(例如 ap-east-1、me-south-1 等选择加入区域)
aws account enable-region \
  --region-name ap-east-1 \
  --region us-east-1

# 禁用一个区域
aws account disable-region \
  --region-name ap-east-1 \
  --region us-east-1

重要: 区域启用/禁用操作需要 account:EnableRegionaccount:DisableRegion IAM 权限,并且必须针对 us-east-1 端点调用,无论目标区域是哪里。

第二部分:Resource Explorer v2 CLI —— Global Search 背后的 API

AWS Resource Explorer v2 是驱动 Global View 中“全局搜索”标签页的底层引擎。与 Global View(仅控制台)不同,Resource Explorer 拥有完整的 API 和 CLI。

2.1 —— 使用聚合索引启用 Resource Explorer

聚合索引是“主索引”,它从所有其他区域的本地索引收集数据:

# 第一步:在你要覆盖的每个区域创建本地索引
for region in ap-south-1 eu-west-1 us-west-2 ap-southeast-1; do
  echo "Creating local index in $region..."
  aws resource-explorer-2 create-index \
    --type LOCAL \
    --region "$region"
done

# 第二步:在 us-east-1 创建聚合索引
aws resource-explorer-2 create-index \
  --type AGGREGATOR \
  --region us-east-1

如果你已在 us-east-1 拥有本地索引,请将其升级为聚合索引:

# 先获取索引 ARN
aws resource-explorer-2 get-index --region us-east-1

# 然后升级
aws resource-explorer-2 update-index-type \
  --arn "arn:aws:resource-explorer-2:us-east-1:123456789012:index/YOUR-INDEX-ID" \
  --type AGGREGATOR \
  --region us-east-1

2.2 —— 创建搜索视图

视图是保存的搜索过滤器——类似跨区域搜索的命名查询:

# 创建显示所有资源(包含所有标签)的视图
aws resource-explorer-2 create-view \
  --view-name "All-Resources-Global" \
  --included-properties Name=tags \
  --region us-east-1

# 创建仅 EC2 的视图
aws resource-explorer-2 create-view \
  --view-name "EC2-Only-Global" \
  --included-properties Name=tags \
  --filters FilterString="service:ec2" \
  --region us-east-1

# 将某个视图设置为该区域的默认视图
aws resource-explorer-2 associate-default-view \
  --view-arn "arn:aws:resource-explorer-2:us-east-1:123456789012:view/All-Resources-Global/YOUR-VIEW-ID"

2.3 —— 通过 CLI 全局搜索资源

# 搜索所有 EC2 实例(全局)
aws resource-explorer-2 search \
  --query-string "resourcetype:ec2:instance" \
  --region us-east-1

# 按标签搜索(例如 Environment=Production)
aws resource-explorer-2 search \
  --query-string "tag.Environment=Production" \
  --region us-east-1

# 搜索所有未打标签的资源
aws resource-explorer-2 search \
  --query-string "-tag.Environment" \
  --region us-east-1

# 搜索所有区域的 RDS 实例
aws resource-explorer-2 search \
  --query-string "service:rds resourcetype:rds:db" \
  --region us-east-1

# 使用特定视图进行 EC2 范围搜索
aws resource-explorer-2 search \
  --query-string "*" \
  --view-arn "arn:aws:resource-explorer-2:us-east-1:123456789012:view/EC2-Only-Global/YOUR-VIEW-ID" \
  --region us-east-1

2.4 —— 列出 Resource Explorer 支持的所有资源类型

# 获取所有支持的资源类型(分页)
aws resource-explorer-2 list-supported-resource-types \
  --max-items 50 \
  --region us-east-1

# 使用 NextToken 获取下一页
aws resource-explorer-2 list-supported-resource-types \
  --max-items 50 \
  --starting-token "NEXT_TOKEN_HERE" \
  --region us-east-1

第三部分:基础设施即代码 —— Terraform

3.1 —— 多区域提供商设置

在 Terraform 中跨多个区域工作时,始终将提供商定义为别名。以下是生产级的 providers.tf

# providers.tf —— 为每个区域定义别名提供商
terraform {
  required_version = ">= 1.6.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.40"
    }
  }
}

# 默认提供商 —— us-east-1(Global View 所在区域)
provider "aws" {
  region = "us-east-1"
}

provider "aws" {
  alias  = "us_east_1"
  region = "us-east-1"
}

provider "aws" {
  alias  = "us_west_2"
  region = "us-west-2"
}

provider "aws" {
  alias  = "ap_south_1"
  region = "ap-south-1"
}

provider "aws" {
  alias  = "eu_west_1"
  region = "eu-west-1"
}

3.2 —— Global View 访问的 IAM 策略和角色

# iam.tf —— 创建最小权限的只读策略和角色
resource "aws_iam_policy" "global_view_readonly" {
  name        = "GlobalViewReadOnlyPolicy"
  description = "Least-privilege read-only policy for AWS Global View and Resource Explorer"
  path        = "/"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "EC2GlobalViewAccess"
        Effect = "Allow"
        Action = [
          "ec2:DescribeRegions",
          "ec2:DescribeInstances",
          "ec2:DescribeVpcs",
          "ec2:DescribeSubnets",
          "ec2:DescribeSecurityGroups",
          "ec2:DescribeVolumes",
          "ec2:DescribeNatGateways",
          "ec2:DescribeInternetGateways",
          "ec2:DescribeRouteTables",
          "ec2:DescribeNetworkAcls",
          "ec2:DescribeNetworkInterfaces",
          "ec2:DescribeAddresses",
          "ec2:DescribeAvailabilityZones",
          "ec2:DescribeVpcPeeringConnections",
          "ec2:DescribeVpcEndpoints",
          "ec2:DescribeManagedPrefixLists",
          "ec2:DescribeAutoScalingGroups"
        ]
        Resource = "*"
      },
      {
        Sid    = "RDSGlobalViewAccess"
        Effect = "Allow"
        Action = [
          "rds:DescribeDBInstances",
          "rds:DescribeDBClusters"
        ]
        Resource = "*"
      },
      {
        Sid    = "S3GlobalViewAccess"
        Effect = "Allow"
        Action = ["s3:ListAllMyBuckets", "s3:GetBucketLocation"]
        Resource = "*"
      },
      {
        Sid    = "ResourceExplorerAccess"
        Effect = "Allow"
        Action = [
          "resource-explorer-2:Search",
          "resource-explorer-2:GetIndex",
          "resource-explorer-2:ListIndexes",
          "resource-explorer-2:ListViews",
          "resource-explorer-2:GetView",
          "resource-explorer-2:ListSupportedResourceTypes"
        ]
        Resource = "*"
      },
      {
        Sid    = "AccountRegionAccess"
        Effect = "Allow"
        Action = [
          "account:GetRegionOptStatus",
          "account:ListRegions"
        ]
        Resource = "*"
      }
    ]
  })
}

resource "aws_iam_role" "global_view_role" {
  name        = "GlobalViewReadOnlyRole"
  description = "IAM Role for Global View access - Lambda or CI/CD"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Service = [
            "lambda.amazonaws.com",
            "ec2.amazonaws.com"
          ]
        }
        Action = "sts:AssumeRole"
      }
    ]
  })
}

resource "aws_iam_role_policy_attachment" "global_view_attach" {
  role       = aws_iam_role.global_view_role.name
  policy_arn = aws_iam_policy.global_view_readonly.arn
}

output "global_view_role_arn" {
  value       = aws_iam_role.global_view_role.arn
  description = "ARN of the Global View read-only IAM role"
}

3.3 —— Terraform:在所有区域启用 Resource Explorer

# resource_explorer.tf —— 创建聚合索引和本地索引
# 在 us-east-1 创建聚合索引
resource "aws_resourceexplorer2_index" "aggregator" {
  provider = aws.us_east_1
  type     = "AGGREGATOR"

  tags = {
    Name        = "global-aggregator-index"
    ManagedBy   = "Terraform"
    Environment = "production"
  }
}

# 在每个区域创建本地索引(聚合索引拉取数据前必须存在)
resource "aws_resourceexplorer2_index" "ap_south_1" {
  provider = aws.ap_south_1
  type     = "LOCAL"

  tags = {
    Name      = "local-index-ap-south-1"
    ManagedBy = "Terraform"
  }

  depends_on = [aws_resourceexplorer2_index.aggregator]
}

resource "aws_resourceexplorer2_index" "us_west_2" {
  provider = aws.us_west_2
  type     = "LOCAL"

  tags = {
    Name      = "local-index-us-west-2"
    ManagedBy = "Terraform"
  }

  depends_on = [aws_resourceexplorer2_index.aggregator]
}

3.4 —— Terraform:创建 Resource Explorer 视图

# views.tf —— 创建跨区域搜索视图
resource "aws_resourceexplorer2_view" "all_resources" {
  provider = aws.us_east_1
  name     = "All-Resources-Global"

  included_property {
    name = "tags"
  }

  depends_on = [aws_resourceexplorer2_index.aggregator]

  tags = {
    ManagedBy = "Terraform"
  }
}

resource "aws_resourceexplorer2_view" "ec2_only" {
  provider = aws.us_east_1
  name     = "EC2-Only-Global"

  filters {
    filter_string = "service:ec2"
  }

  included_property {
    name = "tags"
  }

  depends_on = [aws_resourceexplorer2_index.aggregator]
}

# 设置为 us-east-1 的默认视图
resource "aws_resourceexplorer2_default_view" "default" {
  provider = aws.us_east_1
  view_arn = aws_resourceexplorer2_view.all_resources.arn
}

output "global_view_arn" {
  value = aws_resourceexplorer2_view.all_resources.arn
}

3.5 —— Terraform:跨区域数据源用于库存

# inventory.tf —— 使用数据源跨区域收集资产信息
data "aws_instances" "us_east_1" {
  provider = aws.us_east_1
  filter {
    name   = "instance-state-name"
    values = ["running", "stopped"]
  }
}

data "aws_instances" "ap_south_1" {
  provider = aws.ap_south_1
  filter {
    name   = "instance-state-name"
    values = ["running", "stopped"]
  }
}

data "aws_vpcs" "us_east_1" {
  provider = aws.us_east_1
}

data "aws_vpcs" "ap_south_1" {
  provider = aws.ap_south_1
}

output "us_east_1_instances" {
  value = {
    ids   = data.aws_instances.us_east_1.ids
    count = length(data.aws_instances.us_east_1.ids)
  }
}

output "ap_south_1_instances" {
  value = {
    ids   = data.aws_instances.ap_south_1.ids
    count = length(data.aws_instances.ap_south_1.ids)
  }
}

output "global_vpc_summary" {
  value = {
    us_east_1_vpcs   = tolist(data.aws_vpcs.us_east_1.ids)
    ap_south_1_vpcs  = tolist(data.aws_vpcs.ap_south_1.ids)
  }
}

第四部分:AWS CDK (Python) —— Global View 基础设施即代码

4.1 —— 项目初始化

# 创建项目目录并初始化 CDK 应用
mkdir aws-global-view-cdk && cd aws-global-view-cdk
python3 -m venv .venv
source .venv/bin/activate

pip install aws-cdk-lib constructs boto3

cdk init app --language python

# 安装 Resource Explorer L1 构造
pip install aws-cdk.aws-resourceexplorer2

4.2 —— CDK 应用入口

# app.py —— CDK 应用入口,部署两个栈
import aws_cdk as cdk
from stacks.global_view_stack import GlobalViewStack
from stacks.resource_explorer_stack import ResourceExplorerStack

app = cdk.App()

# 在 us-east-1 部署 IAM 和 Resource Explorer
GlobalViewStack(
    app,
    "GlobalViewStack",
    env=cdk.Environment(region="us-east-1"),
)

ResourceExplorerStack(
    app,
    "ResourceExplorerStack",
    env=cdk.Environment(region="us-east-1"),
)

app.synth()

4.3 —— Global View IAM 栈

# stacks/global_view_stack.py —— 创建只读策略和角色
from aws_cdk import (
    Stack,
    aws_iam as iam,
    CfnOutput,
    Tags,
)
from constructs import Construct

class GlobalViewStack(Stack):
    def __init__(self, scope: Construct, construct_id: str, **kwargs):
        super().__init__(scope, construct_id, **kwargs)

        # 创建 Global View 只读托管策略
        policy = iam.ManagedPolicy(
            self,
            "GlobalViewPolicy",
            managed_policy_name="GlobalViewReadOnlyPolicy",
            statements=[
                iam.PolicyStatement(
                    sid="EC2GlobalView",
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "ec2:DescribeRegions",
                        "ec2:DescribeInstances",
                        "ec2:DescribeVpcs",
                        "ec2:DescribeSubnets",
                        "ec2:DescribeSecurityGroups",
                        "ec2:DescribeVolumes",
                        "ec2:DescribeNatGateways",
                        "ec2:DescribeInternetGateways",
                        "ec2:DescribeRouteTables",
                        "ec2:DescribeNetworkAcls",
                        "ec2:DescribeAddresses",
                        "ec2:DescribeAvailabilityZones",
                        "ec2:DescribeVpcPeeringConnections",
                        "ec2:DescribeVpcEndpoints",
                    ],
                    resources=["*"],
                ),
                iam.PolicyStatement(
                    sid="RDSAndS3Access",
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "rds:DescribeDBInstances",
                        "rds:DescribeDBClusters",
                        "s3:ListAllMyBuckets",
                        "s3:GetBucketLocation",
                    ],
                    resources=["*"],
                ),
                iam.PolicyStatement(
                    sid="ResourceExplorerSearch",
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "resource-explorer-2:Search",
                        "resource-explorer-2:GetIndex",
                        "resource-explorer-2:ListIndexes",
                        "resource-explorer-2:ListViews",
                        "resource-explorer-2:GetView",
                    ],
                    resources=["*"],
                ),
                iam.PolicyStatement(
                    sid="AccountAccess",
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "account:GetRegionOptStatus",
                        "account:ListRegions",
                    ],
                    resources=["*"],
                ),
            ],
        )

        # 创建 IAM 角色,允许 Lambda 和 EC2 使用
        role = iam.Role(
            self,
            "GlobalViewRole",
            role_name="GlobalViewReadOnlyRole",
            assumed_by=iam.CompositePrincipal(
                iam.ServicePrincipal("lambda.amazonaws.com"),
                iam.ServicePrincipal("ec2.amazonaws.com"),
            ),
            managed_policies=[
                policy,
                iam.ManagedPolicy.from_aws_managed_policy_name(
                    "service-role/AWSLambdaBasicExecutionRole"
                ),
            ],
        )

        Tags.of(self).add("ManagedBy", "CDK")
        Tags.of(self).add("Purpose", "GlobalView")

        CfnOutput(self, "RoleArn", value=role.role_arn)
        CfnOutput(self, "PolicyArn", value=policy.managed_policy_arn)

4.4 —— Resource Explorer CDK 栈

# stacks/resource_explorer_stack.py —— 创建聚合索引和视图
from aws_cdk import (
    Stack,
    aws_resourceexplorer2 as rex,
    CfnOutput,
)
from constructs import Construct

class ResourceExplorerStack(Stack):
    def __init__(self, scope: Construct, construct_id: str, **kwargs):
        super().__init__(scope, construct_id, **kwargs)

        # 在 us-east-1 创建聚合索引
        aggregator_index = rex.CfnIndex(
            self,
            "AggregatorIndex",
            type="AGGREGATOR",
            tags=[{"key": "ManagedBy", "value": "CDK"}],
        )

        # 创建“所有资源”视图(包含标签)
        global_view = rex.CfnView(
            self,
            "AllResourcesView",
            view_name="All-Resources-Global-CDK",
            included_properties=[
                rex.CfnView.IncludedPropertyProperty(name="tags")
            ],
            depends_on=[aggregator_index],
        )

        # 创建仅 EC2 的视图
        ec2_view = rex.CfnView(
            self,
            "EC2OnlyView",
            view_name="EC2-Only-Global-CDK",
            filters=rex.CfnView.FiltersProperty(
                filter_string="service:ec2"
            ),
            included_properties=[
                rex.CfnView.IncludedPropertyProperty(name="tags")
            ],
            depends_on=[aggregator_index],
        )

        # 设置默认视图
        rex.CfnDefaultViewAssociation(
            self,
            "DefaultView",
            view_arn=global_view.attr_view_arn,
        )

        CfnOutput(self, "AggregatorIndexArn",
                  value=aggregator_index.attr_arn)
        CfnOutput(self, "GlobalViewArn",
                  value=global_view.attr_view_arn)
        CfnOutput(self, "EC2ViewArn",
                  value=ec2_view.attr_view_arn)

第五部分:Python(Boto3)—— 构建你自己的 Global View

5.1 —— 获取所有已启用区域

import boto3

def get_enabled_regions() -> list[str]:
    """返回所有已启用(选择加入或默认)的区域列表。"""
    ec2 = boto3.client("ec2", region_name="us-east-1")
    response = ec2.describe_regions(
        Filters=[{
            "Name": "opt-in-status",
            "Values": ["opt-in-not-required", "opted-in"]
        }]
    )
    return sorted([r["RegionName"] for r in response["Regions"]])

if __name__ == "__main__":
    regions = get_enabled_regions()
    print(f"Enabled regions ({len(regions)}): {regions}")

5.2 —— 多线程全局 EC2 库存

使用 ThreadPoolExecutor 模拟 Global View 并行获取所有区域的方式——对于 20 多个区域来说,性能至关重要:

import boto3
import json
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime

def get_instances_in_region(region: str) -> list[dict]:
    """分页获取单个区域的 EC2 实例信息。"""
    ec2 = boto3.client("ec2", region_name=region)
    paginator = ec2.get_paginator("describe_instances")
    instances = []
    try:
        for page in paginator.paginate():
            for reservation in page["Reservations"]:
                for inst in reservation["Instances"]:
                    # 提取 Name 标签
                    name = next(
                        (t["Value"] for t in inst.get("Tags", [])
                         if t["Key"] == "Name"),
                        "N/A"
                    )
                    instances.append({
                        "Region": region,
                        "InstanceId": inst["InstanceId"],
                        "Name": name,
                        "State": inst["State"]["Name"],
                        "InstanceType": inst["InstanceType"],
                        "PrivateIp": inst.get("PrivateIpAddress", "N/A"),
                        "PublicIp": inst.get("PublicIpAddress", "N/A"),
                        "LaunchTime": inst["LaunchTime"].strftime("%Y-%m-%d %H:%M:%S"),
                        "VpcId": inst.get("VpcId", "N/A"),
                    })
    except Exception as e:
        print(f"[ERROR] {region}: {e}")
    return instances

def global_ec2_inventory(max_workers: int = 15) -> list[dict]:
    regions = get_enabled_regions()
    all_instances = []
    print(f"Scanning {len(regions)} regions with {max_workers} threads...")

    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        future_map = {
            executor.submit(get_instances_in_region, r): r
            for r in regions
        }
        for future in as_completed(future_map):
            region = future_map[future]
            result = future.result()
            count = len(result)
            if count > 0:
                print(f"  ✅ {region}: {count} instance(s)")
            else:
                print(f"  ⬜ {region}: 0 instances")
            all_instances.extend(result)

    return all_instances

if __name__ == "__main__":
    inventory = global_ec2_inventory()
    print(f"\nTotal instances found: {len(inventory)}")

5.3 —— 完整全局资源摘要(Global View 等价物)

此函数复制了区域探索器顶部“摘要”部分的功能:

import boto3
from concurrent.futures import ThreadPoolExecutor, as_completed

def get_region_summary(region: str) -> dict:
    """收集单个区域的资源计数。"""
    ec2 = boto3.client("ec2", region_name=region)
    rds = boto3.client("rds", region_name=region)
    summary = {
        "Region": region,
        "Instances": 0,
        "VPCs": 0,
        "Volumes": 0,
        "SecurityGroups": 0,
        "Subnets": 0,
        "NatGateways": 0,
        "InternetGateways": 0,
        "ElasticIPs": 0,
        "DBInstances": 0,
        "Error": None,
    }
    try:
        summary["Instances"] = sum(
            len(r["Instances"])
            for r in ec2.describe_instances()["Reservations"]
        )
        summary["VPCs"] = len(ec2.describe_vpcs()["Vpcs"])
        summary["Volumes"] = len(ec2.describe_volumes()["Volumes"])
        summary["SecurityGroups"] = len(
            ec2.describe_security_groups()["SecurityGroups"]
        )
        summary["Subnets"] = len(ec2.describe_subnets()["Subnets"])
        summary["NatGateways"] = len(
            ec2.describe_nat_gateways(
                Filters=[{"Name": "state", "Values": ["available"]}]
            )["NatGateways"]
        )
        summary["InternetGateways"] = len(
            ec2.describe_internet_gateways()["InternetGateways"]
        )
        summary["ElasticIPs"] = len(
            ec2.describe_addresses()["Addresses"]
        )
        summary["DBInstances"] = len(
            rds.describe_db_instances()["DBInstances"]
        )
    except Exception as e:
        summary["Error"] = str(e)
    return summary

def generate_global_view_report() -> list[dict]:
    regions = get_enabled_regions()
    report = []
    with ThreadPoolExecutor(max_workers=15) as executor:
        futures = {
            executor.submit(get_region_summary, r): r
            for r in regions
        }
        for future in as_completed(futures):
            result = future.result()
            report.append(result)

    # 按实例数量降序排序(类似 Global View 的默认排序)
    report.sort(key=lambda x: x.get("Instances", 0), reverse=True)
    return report

5.4 —— 导出报告为 CSV

import csv
from datetime import datetime

def export_to_csv(report: list[dict], filename: str = None):
    if not filename:
        ts = datetime.now().strftime("%Y%m%d_%H%M%S")
        filename = f"global_view_report_{ts}.csv"

    fields = [
        "Region", "Instances", "VPCs", "Volumes",
        "SecurityGroups", "Subnets", "NatGateways",
        "InternetGateways", "ElasticIPs", "DBInstances", "Error"
    ]

    with open(filename, "w", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=fields, extrasaction="ignore")
        writer.writeheader()
        writer.writerows(report)

    print(f"✅ Report exported: {filename}")
    return filename

# 运行完整管道
report = generate_global_view_report()
export_to_csv(report)

5.5 —— 安全审计:全局查找开放的安全组

def find_open_security_groups(port: int = 22) -> list[dict]:
    """
    扫描所有区域,查找将指定端口开放给 0.0.0.0/0 的安全组。
    默认端口为 SSH(22)。常见端口还有 RDP(3389)。
    """
    def scan_region(region):
        ec2 = boto3.client("ec2", region_name=region)
        risky_sgs = []
        try:
            sgs = ec2.describe_security_groups(
                Filters=[
                    {"Name": "ip-permission.from-port", "Values": [str(port)]},
                    {"Name": "ip-permission.to-port", "Values": [str(port)]},
                    {"Name": "ip-permission.cidr", "Values": ["0.0.0.0/0"]},
                ]
            )["SecurityGroups"]

            for sg in sgs:
                risky_sgs.append({
                    "Region": region,
                    "GroupId": sg["GroupId"],
                    "GroupName": sg["GroupName"],
                    "VpcId": sg.get("VpcId", "EC2-Classic"),
                    "Description": sg.get("Description", ""),
                })
        except Exception:
            pass
        return risky_sgs

    regions = get_enabled_regions()
    all_risky = []
    with ThreadPoolExecutor(max_workers=15) as executor:
        results = executor.map(scan_region, regions)
        for result in results:
            all_risky.extend(result)

    print(f"⚠️  Found {len(all_risky)} security groups with port {port} open to 0.0.0.0/0")
    return all_risky

# 查找 SSH 暴露
ssh_exposed = find_open_security_groups(port=22)
rdp_exposed = find_open_security_groups(port=3389)

5.6 —— FinOps:全局查找孤立资源

def find_orphaned_resources() -> dict:
    """
    识别所有区域中未挂载的 EBS 卷和未关联的弹性 IP——
    这些是 AWS 中常见的成本浪费源。
    """
    def scan_region(region):
        ec2 = boto3.client("ec2", region_name=region)
        orphans = {"region": region, "unattached_volumes": [], "unused_eips": []}
        try:
            # 未挂载的 EBS 卷(状态为 available)
            vols = ec2.describe_volumes(
                Filters=[{"Name": "status", "Values": ["available"]}]
            )["Volumes"]
            orphans["unattached_volumes"] = [
                {
                    "VolumeId": v["VolumeId"],
                    "SizeGB": v["Size"],
                    "Type": v["VolumeType"],
                    "EstimatedMonthlyCost": round(v["Size"] * 0.10, 2),  # 大约 $0.10/GB-月(gp2)
                }
                for v in vols
            ]

            # 未关联任何实例的弹性 IP
            eips = ec2.describe_addresses()["Addresses"]
            orphans["unused_eips"] = [
                {
                    "AllocationId": e.get("AllocationId"),
                    "PublicIp": e["PublicIp"],
                    "EstimatedMonthlyCost": 3.65,  # 大约 $0.005/小时闲置 EIP
                }
                for e in eips
                if "AssociationId" not in e
            ]
        except Exception:
            pass
        return orphans

    regions = get_enabled_regions()
    results = {}
    total_vol_cost = 0
    total_eip_cost = 0

    with ThreadPoolExecutor(max_workers=15) as executor:
        for result in executor.map(scan_region, regions):
            region = result["region"]
            if result["unattached_volumes"] or result["unused_eips"]:
                results[region] = result
                total_vol_cost += sum(
                    v["EstimatedMonthlyCost"]
                    for v in result["unattached_volumes"]
                )
                total_eip_cost += sum(
                    e["EstimatedMonthlyCost"]
                    for e in result["unused_eips"]
                )

    print(f"\n💸 Estimated monthly waste:")
    print(f"   Unattached volumes: ${total_vol_cost:.2f}/month")
    print(f"   Unused Elastic IPs: ${total_eip_cost:.2f}/month")
    print(f"   Total:              ${total_vol_cost + total_eip_cost:.2f}/month")
    return results

orphans = find_orphaned_resources()

5.7 —— Resource Explorer Python 客户端

使用 resource-explorer-2 boto3 客户端进行编程式跨区域搜索:

import boto3

def resource_explorer_search(query: str, view_arn: str = None) -> list[dict]:
    """
    使用 AWS Resource Explorer v2 API 搜索资源。
    需要在 us-east-1 有一个活跃的聚合索引。
    """
    client = boto3.client("resource-explorer-2", region_name="us-east-1")
    kwargs = {"QueryString": query}
    if view_arn:
        kwargs["ViewArn"] = view_arn

    resources = []
    paginator = client.get_paginator("search")
    try:
        for page in paginator.paginate(**kwargs):
            for resource in page.get("Resources", []):
                resources.append({
                    "Arn": resource["Arn"],
                    "Region": resource["Region"],
                    "ResourceType": resource["ResourceType"],
                    "Service": resource["Service"],
                    "LastReportedAt": str(resource.get("LastReportedAt", "")),
                    "Tags": {
                        tag["Key"]: tag["Value"]
                        for prop in resource.get("Properties", [])
                        if prop["Name"] == "tags"
                        for tag in prop.get("Data", []),
                    },
                })
    except client.exceptions.UnauthorizedException:
        print("❌ Resource Explorer not enabled or no aggregator index found.")
    return resources

# 搜索所有未打标签的 EC2 实例
untagged = resource_explorer_search(
    query="resourcetype:ec2:instance -tag.Environment"
)
print(f"Untagged EC2 instances: {len(untagged)}")

# 按标签搜索生产资源
prod_resources = resource_explorer_search(
    query="tag.Environment=production"
)
print(f"Production-tagged resources: {len(prod_resources)}")

第六部分:使用 Lambda 自动化 Global View 报告

部署一个每日运行的 Lambda 函数,生成完整的 Global View 报告,并在检测到浪费时通过 SNS 发送成本预警:

# lambda_function.py —— 每日全球资源报告 Lambda 函数
import boto3
import json
import os
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime

SNS_TOPIC_ARN = os.environ.get("SNS_TOPIC_ARN")
S3_BUCKET = os.environ.get("REPORT_BUCKET")
WASTE_THRESHOLD_USD = float(os.environ.get("WASTE_THRESHOLD", "50"))

def lambda_handler(event, context):
    regions = get_enabled_regions()
    report = []
    total_waste = 0.0

    with ThreadPoolExecutor(max_workers=15) as executor:
        futures = {
            executor.submit(get_region_summary, r): r
            for r in regions
        }
        for future in as_completed(futures):
            result = future.result()
            report.append(result)

    # 按实例数量降序排序
    report.sort(key=lambda x: x.get("Instances", 0), reverse=True)

    # 计算总资源数
    total_instances = sum(r.get("Instances", 0) for r in report)
    total_vpcs = sum(r.get("VPCs", 0) for r in report)
    total_volumes = sum(r.get("Volumes", 0) for r in report)

    # 保存到 S3
    ts = datetime.utcnow().strftime("%Y/%m/%d")
    key = f"global-view-reports/{ts}/report.json"
    s3 = boto3.client("s3")
    s3.put_object(
        Bucket=S3_BUCKET,
        Key=key,
        Body=json.dumps(report, default=str),
        ContentType="application/json",
    )
    print(f"Report saved to s3://{S3_BUCKET}/{key}")

    # 如果浪费超过阈值,发送 SNS 警报
    if total_waste > WASTE_THRESHOLD_USD and SNS_TOPIC_ARN:
        sns = boto3.client("sns")
        sns.publish(
            TopicArn=SNS_TOPIC_ARN,
            Subject="⚠️ AWS Global View - Waste Alert",
            Message=(
                f"Estimated monthly waste detected: ${total_waste:.2f}\n\n"
                f"Global Summary:\n"
                f"  Regions scanned: {len(regions)}\n"
                f"  Total EC2 Instances: {total_instances}\n"
                f"  Total VPCs: {total_vpcs}\n"
                f"  Total EBS Volumes: {total_volumes}\n\n"
                f"Full report: s3://{S3_BUCKET}/{key}"
            ),
        )

    return {
        "statusCode": 200,
        "regions_scanned": len(regions),
        "total_instances": total_instances,
        "total_vpcs": total_vpcs,
        "report_location": f"s3://{S3_BUCKET}/{key}",
    }

使用 EventBridge 设置调度:

# 创建 EventBridge 规则——每天 UTC 时间 7:00 运行
aws events put-rule \
  --name "GlobalViewDailyReport" \
  --schedule-expression "cron(0 7 * * ? *)" \
  --state ENABLED

# 附加 Lambda 目标
aws events put-targets \
  --rule "GlobalViewDailyReport" \
  --targets "Id=GlobalViewLambda,Arn=arn:aws:lambda:us-east-1:123456789012:function:GlobalViewReport"

第七部分:真实世界用例

以下场景中,AWS Global View(及其程序化等效方案)能发挥实际的业务价值:

  • 灾难恢复审计:在故障转移测试之前,确认备用 EC2 实例和复制的 RDS 集群存在于你的 DR 区域中。
  • FinOps 与成本优化:每月运行孤立资源脚本——未挂载的卷和未使用的弹性 IP 会在被遗忘的区域中悄无声息地消耗预算。
  • 安全合规:使用开放安全组扫描器全局检测 SSH/RDP 暴露;将其集成到 CI/CD 流水线中作为部署前检查门。
  • 标签强制:使用 Resource Explorer 的 -tag.CostCenter 查询查找所有缺少强制计费标签的资源——在月末费用分摊之前至关重要。
  • 区域治理:禁用组织从不使用的选择加入区域,以减少攻击面并防止意外部署。
  • 迁移验证:在 AWS 区域迁移后,使用全局库存确认源区域中没有任何残留资源。

第八部分:局限性及专家最佳实践

局限性

  • Global View 是只读的——请与 AWS Config 规则配合使用以实现策略执行。
  • 它只覆盖 EC2/VPC 资源家族——Lambda 函数、ECS 集群、CloudFront 分发不会显示在 Global View 中(请使用 Resource Explorer 查看这些资源)。
  • Resource Explorer 需要在 us-east-1 有一个聚合索引,并且需要在你想覆盖的其他所有区域拥有本地索引——这不是免费的,但非常便宜。
  • 对于每个区域资源超过 1000 个的账户,必须使用 Boto3 分页——始终使用分页器,不要使用默认限制的原始 API 调用。

专家最佳实践

  • 始终使用 ThreadPoolExecutormax_workers=15(AWS 默认速率限制允许每个区域每个账户约 20 个并发 describe 调用)。
  • 缓存区域列表到 SSM Parameter Store 或 DynamoDB——每天数千次调用 describe_regions 会增加延迟并消耗 API 配额。
  • 为所有资源打标签——如果没有一致的标签策略,Global View 和 Resource Explorer 的基于标签的搜索将毫无用处;请在组织级别使用 SCP(服务控制策略)强制标签。
  • 与 AWS Config 结合——Global View 告诉你有什么;AWS Config 告诉你它是否合规。
  • 针对多区域设置使用 Terraform 远程状态——将状态存储在 S3 中并配合 DynamoDB 锁定,每个区域每个环境一个状态文件。
  • 将 AGGREGATOR 索引设置在 us-east-1——所有 Resource Explorer 搜索调用都必须指向聚合区域;仅对本地索引区域的调用只会返回本地结果。

总结

AWS Global View 是 AWS 中最被低估但立即可用的工具之一。在控制台中,它让你零设置、零配置,即刻获得多区域可见性。再搭配 AWS CLI 进行脚本化、Terraform 和 CDK 进行基础设施即代码、Boto3 进行自动化,你可以构建一个完全可编程、始终新鲜且远超控制台功能的全局库存系统——包括成本浪费检测、安全审计、合规报告和定时告警。

直达网址:https://console.aws.amazon.com/awsglobalview/home?region=us-east-1#RegionExplorer

类似文章