☰
跨云测试工具链实战:AWS与Azure双云环境如何做到一套用例并行跑
2026/10/7 22:56:59 网站建设 项目流程

去年有段时间,我们团队陷入了一个非常尴尬的处境:业务方要求应用必须同时在 AWS 和 Azure 上跑,可测试这边还是各测各的。QA 同学在 AWS 上写好一套脚本,到了 Azure 上几乎全废,因为两朵云的 API 风格、资源模型、权限体系完全是两套逻辑。后来我们决定把跨云测试工具链这件事认真做起来,目标很简单:同一套测试用例,能在 AWS 和 Azure 上并行跑起来,结果汇总到一份报告里,任何人不用关心底层是哪朵云,只管看业务过没过。这篇文章就是这次集成方案从设计到落地的完整复盘,适合正在做多云战略、需要在双云环境里跑功能测试和接口测试的测试开发、DevOps 工程师参考。

1. 双云环境下的测试痛点:API哲学差异、结果不可比与重复建设

1.1 两朵云的API哲学从根上就不一样

先把痛点说透。很多团队一开始觉得“跨云测试”就是把脚本里的 SDK 换一下,跑起来就行。真做了才发现,AWS 和 Azure 对外暴露的 API 设计哲学差得非常远。

AWS 的 API 核心是偏 RPC 风格,一切操作围绕服务端点发请求,参数通过 Action 区分。你创建一个 EC2 实例,实际请求里带的是Action=RunInstances、ImageId、InstanceType这些 KV 参数。而 Azure 的核心是 Resource Manager 这套 RESTful 资源管理模型,创建一台虚拟机要走Microsoft.Compute/virtualMachines/write这个资源操作,请求路径里带着订阅 ID、资源组、资源名,请求体是一大坨结构化的 JSON。打个不太严谨的比方:AWS 像是给每个动作单独发一张指令卡,Azure 更像是填一张有严格格式要求的资源申请表。

这意味着什么?意味着任何直接调用单云原生 API 写的测试代码,换朵云之后基本都要推翻重来。你在这边封装好的create_vm()函数,到那边参数结构、返回结构、异步逻辑全变了。

1.2 环境不一致导致测试结果根本没法比

第二类痛点是环境漂移。假设你在 AWS 上测性能,跑出来的数据是Standard_B2s的规格,在 Azure 上对应的是Standard_B2s吗?其实不对。AWS 的t3.micro和 Azure 的Standard_B1s算力指标、网络带宽、磁盘 IO 都不一样,甚至连“同一规格”的实例类型映射都找不到完美的对应关系。

更麻烦的是网络和权限。AWS 的默认 VPC 一键就有,Azure 上你得先建虚拟网络、子网、网络安全组,否则机器都起不来。两边的手工配置一旦不一致,同样的测试用例跑出来的结果就没有可比性,出了问题也无从排查是应用代码的问题还是底层环境配置的问题。所以做跨云测试工具链,第一件事就是把两朵云的环境定义变成代码,保证每次测试跑在“跑得起来且可对齐”的环境中。

1.3 重复建设的隐性成本比想象中高

第三个痛点就是大家都能预见的:重复建设。两套脚本、两套执行流水线、两套报告格式,每次业务需求变更,测试代码要同步改两遍。表面上只是“多一倍工作量”,实际上因为平台差异,很多时候是“逻辑一样但代码完全不同”,维护成本并不是线性增长,而是指数增长。

我们当时的判断很明确:这个问题的根子不在脚本层,而在架构层。只有搭建一个工具链,把平台差异在这个链条里隔离掉,才能让测试同学把精力花在业务上,而不是花在跟云厂商 API 搏斗上。

2. 工具链底座设计:Terraform管资源、抽象层管差异、框架管执行

2.1 为什么选Terraform而不是两套原生编排

一说到资源编排,做过云的人会自然想到 AWS 的 CloudFormation 和 Azure 的 ARM Template。但跨云工具链里再分两套编排方案,等于把重复建设的问题又引回来了。我们最终选了 Terraform,理由很直接:一套 HCL 语法同时管 AWS 和 Azure,状态文件可以统一管理,而且社区生态成熟,遇到问题随便一搜都有答案。

Terraform 的多云配置其实非常简单,就是声明两个 provider:

terraform { required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } azurerm = { source = "hashicorp/azurerm" version = "~> 3.0" } } }

实际使用中要注意,Terraform 的工作目录要跟着云走。我们采用的是同一个 modules 仓库,但每个云各维护一套 root module。以“创建测试虚拟机”这个模块为例,AWS 侧和 Azure 侧各有自己的实现,但对外暴露的变量保持一致:name、vpc_id或vnet_id、subnet_id、instance_type或vm_size。调用方完全不用关心底层差异,只需要传业务参数。

2.2 模块化设计:一个测试环境一套独立资源栈

工具链的资源编排我们按“环境维度”划分,而不是按“云维度”划分。什么意思?就是每个测试环境在 AWS 和 Azure 上各有一整套完整的资源栈:网络、安全组、计算实例、对象存储、甚至数据库。

这样做的好处是环境之间的隔离性非常干净。跑测试的时候,你很清楚当前这组用例对应的资源都在哪儿,测完直接整栈销毁,不会残留垃圾资源。模块库的组织方式大概长这样:

terraform/ ├── modules/ │ ├── network/ # VPC / VNet 子网、路由、网关 │ ├── compute/ # EC2 / VM 实例 │ ├── storage/ # S3 / Blob Storage 桶与策略 │ └── security/ # IAM 角色 / SPN 授权 ├── environments/ │ ├── aws-test/ │ │ ├── main.tf │ │ └── terraform.tfvars │ └── azure-test/ │ ├── main.tf │ └── terraform.tfvars

2.3 统一抽象层:在每个云适配器里做隔离

有了资源编排,下一步是给测试代码提供一个统一抽象层。我们的做法是定义一组纯业务接口,然后分别实现 AWS 适配器和 Azure 适配器。以 Python 为例,核心接口大致是:

class CloudAdapter(ABC): @abstractmethod def create_vm(self, spec: VMSpec) -> VMInfo: """创建一台测试虚拟机""" @abstractmethod def destroy_vm(self, vm_id: str) -> None: """销毁虚拟机""" @abstractmethod def get_public_ip(self, vm_id: str) -> str: """获取机器公网IP""" @abstractmethod def put_object(self, bucket: str, key: str, data: bytes) -> None: """向对象存储写入测试数据"""

AWS 适配器里调用boto3,Azure 适配器里调用azure-mgmt-compute和azure-storage-blob,接口层完全一致。测试用例只依赖CloudAdapter这个抽象,不依赖具体实现。这样换代云、换 SDK、换 API 版本的时候,影响面被限制在适配器内部。

3. AWS侧集成落地:IAM权限收敛、VPC隔离与资源生命周期管理

3.1 IAM权限收敛:给测试一张最小权限卡

AWS 侧集成第一步不是写代码,而是把权限设计好。当时我踩过一个坑:图省事直接给测试账号绑了AdministratorAccess,结果某次自动化脚本误删了一个生产标签的资源,差点出事。后来统一改成最小权限方案,专门创建一个test-runner用户,只给测试资源相关的操作权限。

IAM Policy 大致长这样:

{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ec2:RunInstances", "ec2:DescribeInstances", "ec2:TerminateInstances", "ec2:CreateTags", "s3:PutObject", "s3:GetObject", "s3:DeleteObject" ], "Resource": "*" } ] }

注意,这里的Resource: "*"仍然偏宽,更严格的方案是给所有测试资源打上env=test标签,然后在 Policy 里用Condition和aws:ResourceTag做二次校验。实际使用中我们还没做到这个粒度,因为测试环境的 VPC 本身已经是独立隔离的,风险可控。

3.2 VPC规划:测试环境与生产环境网络彻底隔离

AWS 侧的资源规划,最重要的一件事是 VPC 网段。我们单独划了几个测试专用 VPC,CIDR 用的是日常不会碰到的网段,比如10.233.0.0/16。这样就算测试代码里有什么网络配置写死,也不会跟生产网段10.0.0.0/16撞车。

VPC 内部的子网也按用途拆分:外层放安全组和 ALB,内层放 EC2 和 RDS。安全组规则只放通测试必须的端口,SSH 只允许跳板机 IP 访问,数据库端口只允许内网互通。这些全部通过 Terraform 模块化定义,新建环境只需改terraform.tfvars里的几个变量。

3.3 生命周期管理:没有自动清理的测试环境都是负债

跨云测试工具链里有一件事必须一开始就做:资源自动清理。测试环境不像生产环境,用完没人记得手动销毁。我们当时在 CI 流水线里加了一个清理步骤,每天晚上跑一次脚本,扫描所有带env=test标签的 EC2、S3 桶和 RDS 实例,超过 24 小时直接终止或删除。

S3 桶的清理要注意,必须先删除桶内所有对象再删桶,否则 DeleteBucket 会报错。EC2 实例终止前要确认没有挂载的弹性 IP 残留,不然会产生不必要的费用。这块用boto3写脚本不到 200 行就能搞定,但收益极大——我们跑了大半年,几乎没有再出现过“测试资源堆积导致账单爆炸”的情况。

4. Azure侧集成落地:SPN凭证、资源组隔离与NSG策略对齐

4.1 Service Principal:Azure侧的身份底座

对应 AWS 的 IAM 用户,Azure 侧是服务主体(Service Principal)。创建方式如下:

az ad sp create-for-rbac \ --name "test-runner" \ --role Contributor \ --scopes /subscriptions/<订阅ID>/resourceGroups/test-rg

执行完会输出appId(对应 Client ID)、password(对应 Client Secret)、tenant(对应 Tenant ID)。这几个值要放到 CI 平台的密钥管理里,配合环境变量读取,不要写进代码仓库。

Azure SDK 和 Terraform 的 azurerm provider 都会自动读一套标准的认证环境变量:

export AZURE_TENANT_ID="<租户ID>" export AZURE_CLIENT_ID="<客户端ID>" export AZURE_CLIENT_SECRET="<客户端密钥>" export AZURE_SUBSCRIPTION_ID="<订阅ID>"

4.2 资源组隔离:Azure版的“环境边界”

Azure 的资源管理单位是资源组,这一点比 AWS 的“全账号打标签”更适合做测试环境隔离。我们给每个测试环境单独建一个资源组,命名规则rg-test-<环境名>-<日期>。测试结束时直接删掉整个资源组即可,Azure 会把组内的 VM、VNet、存储、公网 IP 一次性清掉,不留残余。

资源组的权限边界也顺便解决了:SPN 的 Role Assignment 范围只到这个资源组,所以即使测试脚本出了 bug,也影响不到其他资源组。

4.3 NSG策略对齐:别让安全组恶心到你

Azure 的网络安全组(NSG)和 AWS 的安全组有个非常容易踩的差异:AWS 安全组默认是隐式拒绝所有流量,只有显式放行才通过;Azure 的 NSG 也是默认拒绝,但规则优先级更高,而且有明确的优先级数字,数字越小越优先。

实际操作中,我们对齐两边策略的方式是在 Terraform 模块里直接定义好默认规则,不让测试同学手工去改。比如:

resource "azurerm_network_security_rule" "allow_ssh" { name = "allow-ssh" priority = 100 direction = "Inbound" access = "Allow" protocol = "Tcp" source_port_range = "*" destination_port_range = "22" source_address_prefixes = ["<跳板机IP>"] destination_address_prefix = "*" }

4.4 AWS和Azure关键服务映射速查

团队里测试同学对两朵云不一定都熟,我们整理了一张服务映射表贴在文档首页,日常写用例时对照着用:

用途AWSAzure
计算实例EC2Virtual Machines
对象存储S3Blob Storage
虚拟网络VPC / SubnetVNet / Subnet
访问控制Security GroupNSG
权限管理IAM PolicyRBAC + Service Principal
日志监控CloudWatchAzure Monitor
数据库RDSAzure SQL / Flexible Server
无服务器计算LambdaFunctions

5. 统一调度与结果聚合:一套用例并行跑双云,报告只出一份

5.1 用例怎么组织才不重复

工具链的核心收敛点在用例管理。我们没搞一套复杂的平台,只是约定:所有用例按业务流程写,云平台作为参数传入,不在用例代码里写死。

每个用例一个 YAML 配置,大致长这样:

test_case: checkout_flow platforms: [aws, azure] params: aws: instance_type: t3.micro region: us-east-1 azure: vm_size: Standard_B1s location: eastus

测试用例代码里通过CloudAdapter获取资源,然后走业务逻辑断言,完全不感知自己在哪朵云上。这样平台的差异只存在于配置文件和适配器层,用例本身是干净的。

5.2 并行执行与结果聚合

执行层面,我们用的是简单的并发模型。CI 流水线里同一个测试任务分两个 job,一个跑 AWS,一个跑 Azure,互不依赖,两边同时发起。启动前由工具链自动调用 Terraform 拉起对应环境的资源栈,跑完自动销毁。

为什么强调并行?因为串行跑双云的时间成本是翻倍的,测试一套业务流动辄十几分钟,串行就意味着用户要等半小时。并行之后,整体耗时基本等于单云耗时,测试效率立刻上来了。

结果聚合是我们自己写的一个小工具,每个云跑完输出一份 JUnit XML 格式的测试报告,聚合器把两份 XML 合并,按用例 ID 分组,生成一个统一的 HTML 报告。报告里专门有一列显示“AWS / Azure 一致性”,如果某个用例在一侧通过另一侧失败,会高亮标出,方便判断是平台问题还是应用问题。

5.3 失败重试策略:先分平台,再重试

跨云测试失败率天然比单云高,因为环境因素多。我们踩过的经验是:不要一失败就盲目重试,先看失败类型。AWS 侧常见的RequestLimitExceeded或 Azure 侧常见的Conflict,属于可以重试的瞬时错误;而断言失败、404 这类说明业务确实有问题,重试只会浪费时间。

工具链里我们给重试加了一个简单的分类机制:瞬时错误最多重试 3 次,每次间隔递增(比如 10 秒、20 秒、40 秒);业务断言类错误直接失败,并把详细日志丢给开发定位。

6. 落地过程中的坑与对策:从凭证管理到成本兜底

6.1 凭证管理:永远不要把AK/SK写进仓库

这个坑几乎每个团队都会踩。我们有个同事图方便,把 AWS Access Key 直接写在测试脚本的环境变量文件里,还顺手提交到了 Git 仓库。所幸发现得早,没有造成实际泄露,但光是轮换凭证就折腾了一整天。

现在的规范是:所有云凭证只存在 CI 平台的密钥管理服务里,本地开发用 sso 或 service principal 的短时凭证,测试脚本里一律通过环境变量注入。Azure 侧的 Secret 定过期时间,到期强制轮换。

6.2 两朵云的区域服务差异

跨云测试工具链里最容易被忽略的就是区域差异。AWS 的某些新服务只在部分 region 提供,Azure 的某些 VM 系列只在特定 region 有配额。你在一朵云上跑得好好的用例,到另一朵云可能连资源都创建不出来。

解决方式是:创建资源前先调用两朵云的配额查询接口,确认当前区域能满足规格要求,不满足就自动 fail-fast 并给提示,而不是等到资源创建失败再排查。这个步骤放在适配器里做,非常值得。

6.3 Terraform状态文件的管理问题

Terraform 的状态文件容易成为团队协作的坑。多个开发同时terraform apply,状态文件很容易冲突。AWS 侧我们把 state 放在 S3 并开启 DynamoDB 锁,Azure 侧放在 Storage Account 的 Blob 里并开启租约锁。两边都配置好之后,基本不会再出现状态文件互相覆盖的问题。

6.4 成本兜底:定时关停和额度上限

测试工具链的东西,平时用着爽,月底账单来了就肉疼。我们做了两件事来控制成本:

一是非工作时间自动关停。通过定时任务,每天晚上八点把所有带env=test标签的 EC2 实例 Stop 掉(Azure 侧对应 Deallocate),第二天早上 CI 触发测试前再 Start。注意 Azure 的 VM 是 Deallocate 才停止计费计算资源,单纯 Power Off 还是会产生计算费用,这个和 AWS 的 Stop 行为也有差异。

二是给每朵云分别设置了费用告警阈值,比如 AWS 侧月度预算超过 X 元就 SNS 通知,Azure 侧超过预算就触发 Alert。成本异常能第一时间发现。

6.5 最终的建议:先跑通最小闭环再扩量

工具链搭完之后,我们最大的体会是:第一版千万不要贪多。最开始只挑了三条核心业务链路在双云上跑通,验证了“资源创建→用例执行→资源销毁→结果聚合”这条主链路可行之后,才逐步把用例量扩上去。如果一开始就想全量覆盖所有业务,大概率会在各种平台差异的泥潭里挣扎得筋疲力尽。

还有个很实用的小技巧:在 CI 结果通知里给两个云各打一个标签。一旦某侧用例挂了,先看是平台相关还是应用本身的问题,这样排查效率会高很多。这套方案跑到现在,跨云测试已经成为我们发布流程里非常稳定的一环,测试同学不用再跟云厂商 API 死磕,可以把精力放回业务本身了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询