K6s 脚本如何复用 Terraform 输出的网关地址并自动发现服务
解读
在国内云原生落地场景中,性能测试工程师往往要“左手 Terraform、右手 K6”,既要保证压测脚本随环境一键拉起,又要让脚本在网关 IP、服务端口变化时零修改。
面试官真正想验证的是:
- 你是否能把 IaC(Terraform)的输出无缝喂给 K6,避免人肉改脚本;
- 你是否理解 K6 的“初始化上下文”与“VU 代码”隔离模型,知道在什么时候、用什么方式把动态地址注入;
- 你是否具备“服务自动发现”思维,能在容器、K8s、微服务网关中用最低成本找到真实后端,而不是写死 IP。
答不到“零侵入、零硬编码”,基本会被判为“只能写死脚本”的初级选手。
知识点
- Terraform output 机制:
- 根模块通过 output 块暴露网关地址,如 output "gw_ip" { value = aws_lb.k8s_ingress.ip }
- 本地落地常用 terraform output -json > tf-out.json 落盘,或 terraform output -raw gw_ip 直接打印
- K6 初始化上下文(init context)与 VU 代码隔离:
- init 阶段可执行 Node.js 兼容语法,能读文件、发请求、做轻量计算
- VU 阶段只能使用 K6 提供的 JS API,不能读文件,因此必须在 init 阶段把变量“固化”到全局
- K6 环境变量与 __ENV 对象:
- 启动时 -e GW_IP=1.2.3.4 或 --env-file tf.env 均可注入
- 脚本中用 let gw = __ENV.GW_IP || 'default' 读取
- 服务自动发现三板斧:
- DNS SRV:在 K6 init 阶段用 dns.resolveSrv('_http._tcp.my-svc.my-ns.svc.cluster.local') 拿到后端列表
- K8s Endpoint API:init 阶段通过集群内 token 调用 https://kubernetes.default.svc/api/v1/namespaces/my-ns/endpoints/my-svc,解析 subsets.addresses.ip
- Consul/Nacos:init 阶段走 HTTP API 拉实例列表,国内 Nacos 更常见,需带 accessToken 与 namespace 参数
- 国内混合云细节:
- 部分金融客户不允许 K6 直接访问 K8s API,需通过跳板“中转服务”把 endpoints 转成 HTTP 接口
- 阿里 MSE 网关、腾讯 TSE 网关的 CLB 域名会带“-internal”后缀,K6 脚本需识别内网/外网场景,自动拼对 Host 头
答案
整体思路:让 Terraform 把网关地址“吐”出来,K6 在 init 阶段消费;同时用 DNS SRV 或 K8s API 在 init 阶段拉取后端实例,做到脚本零硬编码。
步骤如下:
-
Terraform 端标准化输出
在根模块增加 outputs.tf:
output "gw_ip" {
value = aws_lb.k8s_ingress.ip
description = "K8s 入口网关 IP,供 K6 压测使用"
}
output "gw_port" {
value = 80
}
执行完 terraform apply 后,本地脚本继续执行:
terraform output -json > tf-out.json -
K6 端在 init 阶段读取 Terraform 输出
新建 k6-script.js:
const tf = JSON.parse(open('tf-out.json'));
const gwIp = tf.gw_ip.value;
const gwPort = tf.gw_port.value;
const baseURL =http://${gwIp}:${gwPort}; -
自动发现后端服务(以 DNS SRV 为例,兼容 CoreDNS)
import { resolveSrv } from 'k6/dns';
const srvRecords = resolveSrv('_http._tcp.cart-svc.default.svc.cluster.local');
const backends = srvRecords.map(r =>${r.target}:${r.port}); -
VU 阶段随机挑一个后端发压
export default function () {
const backend = backends[Math.floor(Math.random() * backends.length)];
const url =${baseURL}/api/cart;
http.get(url, { headers: { Host: 'cart.example.com' } });
} -
一键启动命令
terraform apply -auto-approve && \
terraform output -json > tf-out.json && \
k6 run --vus 200 --duration 5m k6-script.js
至此,网关 IP、后端列表全部动态注入,脚本无需任何硬编码,满足国内持续集成流水线“一次构建、任意环境运行”的硬性要求。
拓展思考
- 如果客户不允许 K6 直接解析 DNS SRV,可以写一个轻量“发现服务”Sidecar,把 endpoints 转成 REST,K6 在 init 阶段用 http.get 拉取即可;该 Sidecar 随 Terraform 一起部署,保证生命周期一致。
- 多网关场景(公网+内网)下,可在 Terraform output 里增加“gw_type”,K6 脚本根据类型自动选择是否走公司代理、是否带 SSL Client Cert,实现“一套脚本压两遍”。
- 当后端实例频繁弹性伸缩时,DNS 缓存会导致 K6 拿到的列表滞后,可在 VU 阶段周期性(例如每 30s)调用 http.get 刷新 backends 数组,但注意 K6 的 VU 代码不能阻塞,需用 setTimeout 伪递归实现非阻塞刷新。
- 国内部分银行要求压测流量必须带“压测标”,可在 Terraform 输出里同时下发 header 标记值,K6 脚本统一注入,避免测试流量误打生产。