将 HTTP 故障注入 Pod
本示例展示了如何使用 PodDisruptor 对 Pod 服务处理的 HTTP 请求注入故障进行测试的效果。
您可以在本文档末尾找到完整的源代码。接下来的部分将详细介绍代码。
本示例使用了httpbin,这是一个简单的请求/响应应用程序,提供了用于测试不同 HTTP 请求的端点。
测试要求 httpbin
部署在集群中的 httpbin
命名空间中,并暴露一个外部 IP。该 IP 地址应在环境变量 SVC_IP
中提供。
您可以在本文档末尾的测试设置部分找到 Kubernetes 清单和部署说明。您可以在暴露您的应用部分了解如何获取外部 IP 地址。
初始化
初始化代码导入测试所需的外部依赖项。从 xk6-disruptor
扩展导入的 PodDisruptor
类提供了用于向 Pod 注入故障的函数。k6/http 模块提供了执行 HTTP 请求的函数。
import { PodDisruptor } from 'k6/x/disruptor';
import http from 'k6/http';
测试负载
测试负载由 default
函数生成,该函数使用从环境变量 SVC_IP
获取的 IP 向 httpbin
Pod 发送请求。测试向端点 delay/0.1
发送请求,该端点将在 0.1
秒 (100ms
) 后返回。
export default function (data) {
http.get(`http://${data.SVC_IP}/delay/0.1`);
}
注意
测试使用了
delay
端点,该端点在请求的延迟后返回。它请求了0.1s
(100ms
) 的延迟,以确保基线场景(见下文场景)具有有意义的请求持续时间统计信息。如果只是调用本地部署的 http 服务器(例如nginx
),响应时间会在几微秒到几毫秒之间呈现很大的变化。事实证明,将100ms
作为基线响应时间可以提供更一致的结果。
故障注入
disrupt
函数使用选择器创建一个 PodDisruptor
,该选择器匹配 httpbin
命名空间中带有标签 app: httpbin
的 Pod。
通过调用 PodDisruptor.injectHTTPFaults 方法注入 HTTP 故障,使用故障定义在每个请求中引入 50ms
的延迟,并在 10%
的请求中引入错误代码 500
。
export function disrupt(data) {
if (__ENV.SKIP_FAULTS == '1') {
return;
}
const selector = {
namespace: namespace,
select: {
labels: {
app: 'httpbin',
},
},
};
const podDisruptor = new PodDisruptor(selector);
// delay traffic from one random replica of the deployment
const fault = {
averageDelay: '50ms',
errorCode: 500,
errorRate: 0.1,
};
podDisruptor.injectHTTPFaults(fault, '30s');
}
请注意上面 injectFaults
函数中的以下代码片段
export function disrupt(data) {
if (__ENV.SKIP_FAULTS == '1') {
return;
}
//...
}
如果测试执行时传递了环境变量 SKIP_FAULTS
且值为“1”,此代码将使函数返回而不注入故障,如故障注入部分所述。我们将使用此选项来获取没有故障的基线执行结果。
场景
此测试定义了两个要执行的场景。load
场景通过调用 default
函数,对 httpbin
应用施加 30s
的测试负载。disrupt
场景调用 disrupt
函数,向目标应用的 HTTP 请求注入故障。
export const options = {
scenarios: {
load: {
executor: 'constant-arrival-rate',
rate: 100,
preAllocatedVUs: 10,
maxVUs: 100,
exec: 'default',
startTime: '0s',
duration: '30s',
},
disrupt: {
executor: 'shared-iterations',
iterations: 1,
vus: 1,
exec: 'disrupt',
startTime: '0s',
},
},
};
注意
请注意,
disrupt
场景使用了一个shared-iterations
执行器,包含一次迭代和一个VU
。此设置确保disrupt
函数只执行一次。并行多次执行此函数可能会产生不可预测的结果。
执行
注意
本节中的命令假设
xk6-disruptor
二进制文件位于当前目录中。此位置可能因安装过程和平台而异。有关如何在您的环境中安装它,请参阅安装部分。
基线执行
我们将首先在不引入故障的情况下执行测试,以获取基线,使用以下命令
xk6-disruptor run --env SKIP_FAULTS=1 --env SVC_IP=$SVC_IP --summary-mode=legacy disrupt-pod.js
xk6-disruptor run --env SKIP_FAULTS=1 --env "SVC_IP=$Env:SVC_IP" --summary-mode=legacy disrupt-pod.js
注意参数 --env SKIP_FAULT=1
,它使得 disrupt
函数返回而不注入任何故障,如故障注入部分所述。另请注意参数 --env SVC_IP
,它传递了用于访问 httpbin
应用的外部 IP。
您应该获得与下面类似的输出(点击 Expand
按钮查看所有输出)。
故障注入
我们重复执行并注入故障。请注意,我们已移除 --env SKIP_FAULTS=1
参数。
xk6-disruptor run --env SVC_IP=$SVC_IP --summary-mode=legacy disrupt-pod.js
xk6-disruptor run --env "SVC_IP=$Env:SVC_IP" --summary-mode=legacy disrupt-pod.js
比较
让我们仔细查看每个场景中请求的结果。我们可以观察到,base
场景的平均响应时间为 103ms
,错误率为 0%
,而 faults
场景的平均响应时间约为 151.9ms
,错误率接近 10%
,这与 disruptor 中定义的故障一致。
执行 | 平均响应时间 | 失败请求 |
---|---|---|
基线 | 103.22ms | 0.00% |
故障注入 | 151.9ms | 9.65% |
注意
请注意,我们使用了报告为
expected_response:true
的平均响应时间,因为此指标仅考虑成功请求,而http_req_duration
考虑所有请求,包括返回故障的请求。
源代码
import { PodDisruptor } from 'k6/x/disruptor';
import http from 'k6/http';
export default function (data) {
http.get(`http://${__ENV.SVC_IP}/delay/0.1`);
}
export function disrupt(data) {
if (__ENV.SKIP_FAULTS == '1') {
return;
}
const selector = {
namespace: 'httpbin',
select: {
labels: {
app: 'httpbin',
},
},
};
const podDisruptor = new PodDisruptor(selector);
// delay traffic from one random replica of the deployment
const fault = {
averageDelay: '50ms',
errorCode: 500,
errorRate: 0.1,
};
podDisruptor.injectHTTPFaults(fault, '30s');
}
export const options = {
scenarios: {
load: {
executor: 'constant-arrival-rate',
rate: 100,
preAllocatedVUs: 10,
maxVUs: 100,
exec: 'default',
startTime: '0s',
duration: '30s',
},
disrupt: {
executor: 'shared-iterations',
iterations: 1,
vus: 1,
exec: 'disrupt',
startTime: '0s',
},
},
};
测试设置
测试需要部署 httpbin
应用。应用还必须能够通过 SVC_IP
环境变量中提供的外部 IP 进行访问。
下面的清单定义了部署应用并将其暴露为 LoadBalancer 服务所需的资源。
您可以使用以下命令部署应用
# Create Namespace
kubectl apply -f namespace.yaml
namespace/httpbin created
# Deploy Pod
kubectl apply -f pod.yaml
pod/httpbin created
# Expose Pod as a Service
kubectl apply -f service.yaml
service/httpbin created
您可以使用以下命令检索资源
kubectl -n httpbin get all
NAME READY STATUS RESTARTS AGE
pod/httpbin 1/1 Running 0 1m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/httpbin LoadBalancer 10.96.169.78 172.18.255.200 80:31224/TCP 1m
您必须将环境变量 SVC_IP
设置为从测试脚本访问 httpbin
服务所使用的外部 IP 地址和端口。
您可以在暴露您的应用部分了解如何获取外部 IP 地址。
清单
apiVersion: 'v1'
kind: Namespace
metadata:
name: httpbin
kind: 'Pod'
apiVersion: 'v1'
metadata:
name: httpbin
namespace: httpbin
labels:
app: httpbin
spec:
containers:
- name: httpbin
image: kennethreitz/httpbin
ports:
- name: http
containerPort: 80
apiVersion: 'v1'
kind: 'Service'
metadata:
name: httpbin
namespace: httpbin
spec:
selector:
app: httpbin
type: 'LoadBalancer'
ports:
- port: 80
targetPort: 80