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-1、ap-south-1 和 eu-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 命令。你可以使用 ec2 和 account 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:EnableRegion和account:DisableRegionIAM 权限,并且必须针对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 调用。
专家最佳实践
- 始终使用 ThreadPoolExecutor,
max_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
