菜单
开源

向服务注入 gRPC 故障

此示例展示了如何使用 ServiceDisruptor 测试注入到服务处理的 gRPC 请求中的故障效果。

完整的源代码位于本文档末尾。下一部分将详细介绍代码。

本示例使用 grpcpbin,这是一个简单的请求/响应应用程序,提供用于测试不同 gRPC 请求的端点。

测试要求将 grpcpbin 部署在集群中的 grpcbin 命名空间中,并通过外部 IP 暴露。该 IP 地址应位于环境变量 GRPC_HOST 中。

有关 Kubernetes manifests 以及如何在集群中部署它的说明,请参阅本文档末尾的测试设置部分。要了解如何获取外部 IP 地址,请参阅暴露您的应用程序

初始化

初始化代码导入测试所需的外部依赖项。从 xk6-disruptor 扩展导入的 ServiceDisruptor 类提供了向服务注入故障的功能。k6/net/grpc 模块提供了执行 gRPC 请求的功能。check 函数用于验证请求结果。

JavaScript
import { ServiceDisruptor } from 'k6/x/disruptor';
import grpc from 'k6/net/grpc';
import { check } from 'k6';

我们还创建了一个 grpc 客户端,并加载了 HelloService 服务的 protobuf 定义。

JavaScript
const client = new grpc.Client();
client.load(['pb'], 'hello.proto');

测试负载

测试负载由 default 函数生成,该函数使用从环境变量 GRPC_HOST 获取的 IP 和端口连接到 grpcbin 服务,并调用 hello.HelloService 服务的 SayHello 方法。最后,检查响应的状态码。当注入故障时,此检查应失败。

JavaScript
export default function () {
  client.connect(__ENV.GRPC_HOST, {
    plaintext: true,
    timeout: '1s',
  });

  const data = { greeting: 'Bert' };
  const response = client.invoke('hello.HelloService/SayHello', data, {
    timeout: '1s',
  });
  client.close();

  check(response, {
    'status is OK': (r) => r && r.status === grpc.StatusOK,
  });
}

故障注入

disrupt 函数为 grpcbin 命名空间中的 grpcbin 服务创建一个 ServiceDisruptor

通过调用 ServiceDisruptor.injectGrpcFaults 方法注入 gRPC 故障,使用的故障定义会在每个请求中引入 300ms 的延迟,并在 10% 的请求中引入状态码为 13(“Internal error”)的错误。

JavaScript
export function disrupt() {
  if (__ENV.SKIP_FAULTS == '1') {
    return;
  }

  const fault = {
    averageDelay: '300ms',
    statusCode: grpc.StatusInternal,
    errorRate: 0.1,
    port: 9000,
  };
  const disruptor = new ServiceDisruptor('grpcbin', 'grpcbin');
  disruptor.injectGrpcFaults(fault, '30s');
}

请注意上面 injectFaults 函数中的以下代码片段

JavaScript
export function disrupt() {
  if (__ENV.SKIP_FAULTS == '1') {
    return;
  }
  //...
}

如果测试执行时传入环境变量 SKIP_FAULTS 的值为“1”,此代码将使函数返回而不注入故障,如故障注入部分所述。另请注意参数 --env GRPC_HOST,它传递用于访问 grpcbin 应用程序的外部 IP。

场景

此测试定义了要执行的两个场景load 场景调用 default 函数,对 grpcpbin 应用程序施加 30s 的测试负载。disrupt 场景调用 disrupt 函数,向目标应用程序的 gRPC 请求注入故障。

JavaScript
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 二进制文件位于您当前的目录中。此位置可能因安装过程和平台而异。有关如何在您的环境中安装它,请参阅安装

基线执行

我们将首先在不引入故障的情况下执行测试,使用以下命令获取基线

bash
xk6-disruptor --env SKIP_FAULTS=1 --env GRPC_HOST=$GRPC_HOST run grpc-faults.js
windows-powershell
xk6-disruptor --env SKIP_FAULTS=1 --env "GRPC_HOST=$Env:GRPC_HOST" run grpc-faults.js

注意参数 --env SKIP_FAULT=1,它会使 disrupt 函数返回而不注入任何故障,如故障注入部分所述。另请注意参数 --env GRPC_HOST,它传递用于访问 grpcbin 应用程序的外部 IP。

您应该会看到类似于以下的输出(使用 Expand 按钮查看所有输出)。

故障注入

我们重复执行并注入故障。注意我们已移除 --env SKIP_FAULTS=1 参数。

bash
xk6-disruptor --env GRPC_HOST=$GRPC_HOST run grpc-faults.js
windows-powershell
xk6-disruptor --env "GRPC_HOST=$Env:GRPC_HOST" run grpc-faults.js

比较

让我们仔细看看每个场景中请求的结果。我们可以观察到,在 base 场景中,请求持续时间的 95 百分位值为 2.09ms,并且 100% 的请求通过检查。而 faults 场景的 96 百分位值为 303.57ms,只有 89% 的请求通过检查(或者换句话说,11% 的请求失败),这与故障定义密切匹配。

执行P95 请求持续时间通过的检查数
基线2.09ms100%
故障注入303.57ms89%

源代码

测试设置

测试需要部署 grpcbin 应用程序。该应用程序还必须能够通过环境变量 GRPC_HOST 中的外部 IP 访问。

以下manifests 定义了部署应用程序并将其暴露为 LoadBalancer 服务所需的资源。

您可以使用以下命令部署应用程序

bash
# Create Namespace
kubectl apply -f namespace.yaml
namespace/grpcbin created

# Deploy Pod
kubectl apply -f pod.yaml
pod/grpcbin created

# Expose Pod as a Service
kubectl apply -f service.yaml
service/grpcbin created

您必须设置环境变量 GRPC_HOST,其中包含从测试脚本访问 grpcbin 服务所需的外部 IP 地址和端口。

要了解如何获取外部 IP 地址,请参阅暴露您的应用程序

Manifests