引入基于 Rust 的 Ztunnel 用于 Istio 环境服务网格

Istio 环境网格的专用每节点代理。

2023年2月28日 | 作者:Lin Sun - Solo.io,John Howard - Google

ztunnel(零信任隧道)组件是 Istio 环境网格的专用每节点代理。它负责安全地连接和认证环境网格内的工作负载。Ztunnel 旨在专注于环境网格中工作负载的一组小型功能,例如 mTLS、身份验证、L4 授权和遥测,而不会终止工作负载 HTTP 流量或解析工作负载 HTTP 标头。Ztunnel 确保流量高效且安全地传输到路标代理,在那里实现 Istio 的完整功能集,例如 HTTP 遥测和负载均衡。

由于 ztunnel 旨在运行在所有 Kubernetes 工作节点上,因此保持其资源占用量小至关重要。Ztunnel 旨在成为服务网格中不可见(或“环境”)的一部分,对工作负载的影响最小。

Ztunnel 架构

类似于 sidecar,ztunnel 也充当 xDS 客户端和 CA 客户端

  1. 在启动期间,它使用其服务帐户令牌安全地连接到 Istiod 控制平面。一旦通过 TLS 安全地建立了从 ztunnel 到 Istiod 的连接,它就开始作为 xDS 客户端获取 xDS 配置。这与 sidecar 或网关或路标代理的工作方式类似,只是 Istiod 识别来自 ztunnel 的请求并发送专为 ztunnel 设计的 xDS 配置,您很快就会了解更多信息。
  2. 它还充当 CA 客户端,代表其管理的所有共置工作负载管理和预配 mTLS 证书。
  3. 随着流量的进出,它充当核心代理,处理其管理的所有共置工作负载的入站和出站流量(无论是网格外明文还是网格内 HBONE)。
  4. 它提供 L4 遥测(指标和日志)以及一个带有调试信息的管理服务器,以帮助您在需要时调试 ztunnel。
Ztunnel architecture
Ztunnel 架构

为什么不重用 Envoy?

当 Istio 环境服务网格于 2022 年 9 月 7 日发布时,ztunnel 是使用 Envoy 代理实现的。鉴于我们对 Istio 的其余部分(sidecar、网关和路标代理)使用 Envoy,因此我们很自然地开始使用 Envoy 实现 ztunnel。

但是,我们发现,虽然 Envoy 非常适合其他用例,但在 Envoy 中实现 ztunnel 却很困难,因为许多权衡、需求和用例与 sidecar 代理或入口网关的权衡、需求和用例大不相同。此外,使 Envoy 非常适合这些其他用例的大多数内容,例如其丰富的 L7 功能集和可扩展性,在 ztunnel 中都被浪费了,因为 ztunnel 不需要这些功能。

专为 ztunnel 设计

在努力将 Envoy 调整到我们的需求后,我们开始研究对 ztunnel 进行专门的实现。我们的假设是,从一开始就以单一聚焦的用例进行设计,我们可以开发出比将通用项目塑造成我们的定制用例更简单、更高效的解决方案。明确决定使 ztunnel 简单是这个假设的关键;例如,类似的逻辑无法支持重写网关,网关具有大量的支持功能和集成。

这个专为 ztunnel 设计涉及两个关键领域

配置协议

Envoy 代理使用 xDS 协议进行配置。这是使 Istio 运行良好的关键部分,它提供了丰富且动态的配置更新。但是,当我们偏离常规路径时,配置变得越来越定制化,这意味着生成配置的成本更高且更大。在一个 sidecar 中,一个带有 1 个 Pod 的单个服务会生成大约 350 行 xDS(以 YAML 格式),这已经很难扩展。基于 Envoy 的 ztunnel 情况更糟,在某些方面具有 N^2 的扩展属性。

为了使 ztunnel 配置尽可能小,我们研究了使用专门的配置协议,该协议以高效的格式包含我们所需的确切信息(仅此而已)。例如,单个 Pod 可以简洁地表示为

name: helloworld-v1-55446d46d8-ntdbk
namespace: default
serviceAccount: helloworld
node: ambient-worker2
protocol: TCP
status: Healthy
waypointAddresses: []
workloadIp: 10.244.2.8
canonicalName: helloworld
canonicalRevision: v1
workloadName: helloworld-v1
workloadType: deployment

这些信息通过 xDS 传输 API 传输,但使用自定义的环境特定类型。请参阅 工作负载 xDS 配置部分,以了解有关配置详细信息的更多信息。

通过拥有专门的 API,我们可以将逻辑推送到代理中,而不是推送到 Envoy 配置中。例如,要在 Envoy 中配置 mTLS,我们需要添加一组相同的较大配置来微调每个服务的精确 TLS 设置;使用 ztunnel,我们只需要一个枚举来声明是否应该使用 mTLS。其余的复杂逻辑直接嵌入到 ztunnel 代码中。

通过 Istiod 和 ztunnel 之间的高效 API,我们发现我们可以使用数量级更少的配置来配置有关大型网格(例如包含 100,000 个 Pod 的网格)的信息,这意味着更少的 CPU、内存和网络成本。

运行时实现

顾名思义,ztunnel 使用 HTTPS 隧道 来承载用户请求。虽然 Envoy 支持此隧道,但我们发现配置模型限制了我们的需求。粗略地说,Envoy 通过一系列“过滤器”发送请求来运行,从接受请求开始到发送请求结束。根据我们的要求,这些要求有多层请求(隧道本身和用户的请求),以及在负载均衡后应用每个 Pod 策略的需求,我们发现我们在实现我们之前的基于 Envoy 的 ztunnel 时,每个连接需要遍历这些过滤器 4 次。虽然 Envoy 对本质上在内存中“向自身发送请求”有一些 优化,但这仍然非常复杂且昂贵。

通过构建我们自己的实现,我们可以从头开始设计这些约束。此外,我们在设计的所有方面都有更大的灵活性。例如,我们可以选择跨线程共享连接或实现围绕服务帐户之间隔离的更多定制要求。在确定专门的代理是可行的之后,我们着手选择实现细节。

基于 Rust 的 Ztunnel

为了使 ztunnel 快速、安全和轻量级,Rust 是一个显而易见的选择。然而,它不是我们的首选。鉴于 Istio 目前广泛使用 Go,我们希望我们可以使基于 Go 的实现达到这些目标。在最初的原型中,我们构建了一些基于 Go 的实现和基于 Rust 的实现的简单版本。根据我们的测试,我们发现基于 Go 的版本没有满足我们的性能和占用空间要求。虽然我们可能可以对其进行进一步优化,但我们认为基于 Rust 的代理将为我们提供长期最佳的实现。

还考虑了 C++ 实现——可能重用 Envoy 的部分内容。但是,由于缺乏内存安全、开发人员体验问题以及行业普遍转向 Rust 的趋势,因此没有采用此选项。

这个排除过程让我们选择了 Rust,它非常适合。Rust 在高性能、低资源利用率的应用程序(尤其是在网络应用程序(包括服务网格)中)方面拥有强大的成功历史。我们选择构建在 TokioHyper 库之上,这两个库是生态系统中的事实标准,经过广泛的实战测试,并且易于编写高性能的异步代码。

基于 Rust 的 Ztunnel 快速浏览

工作负载 xDS 配置

工作负载 xDS 配置非常易于理解和调试。您可以通过向 ztunnel Pod 之一的 localhost:15000/config_dump 发送请求来查看它们,或者使用方便的 istioctl pc workload 命令。有两个关键的工作负载 xDS 配置:工作负载和策略。

在您的工作负载包含在您的环境网格中之前,您仍然可以在 ztunnel 的配置转储中看到它们,因为 ztunnel 了解所有工作负载,无论它们是否启用了环境。例如,以下包含新部署的 helloworld v1 Pod 的示例工作负载配置,该 Pod 位于网格外,由 protocol: TCP 指示

{
  "workloads": {
    "10.244.2.8": {
      "workloadIp": "10.244.2.8",
      "protocol": "TCP",
      "name": "helloworld-v1-cross-node-55446d46d8-ntdbk",
      "namespace": "default",
      "serviceAccount": "helloworld",
      "workloadName": "helloworld-v1-cross-node",
      "workloadType": "deployment",
      "canonicalName": "helloworld",
      "canonicalRevision": "v1",
      "node": "ambient-worker2",
      "authorizationPolicies": [],
      "status": "Healthy"
    }
  }
}

在 Pod 包含在环境中(通过为命名空间 default 添加标签 istio.io/dataplane-mode=ambient)后,protocol 值将替换为 HBONE,指示 ztunnel 将 helloworld-v1 Pod 的所有传入和传出通信升级为 HBONE。

{
  "workloads": {
    "10.244.2.8": {
      "workloadIp": "10.244.2.8",
      "protocol": "HBONE",
      ...
}

在您部署任何工作负载级授权策略后,策略配置将作为 xDS 配置从 Istiod 推送到 ztunnel 并显示在 policies

{
  "policies": {
    "default/hw-viewer": {
      "name": "hw-viewer",
      "namespace": "default",
      "scope": "WorkloadSelector",
      "action": "Allow",
      "groups": [[[{
        "principals": [{"Exact": "cluster.local/ns/default/sa/sleep"}]
      }]]]
    }
  }
  ...
}

您还会注意到工作负载的配置已更新,其中包含对授权策略的引用。

{
  "workloads": {
    "10.244.2.8": {
    "workloadIp": "10.244.2.8",
    ...
    "authorizationPolicies": [
        "default/hw-viewer"
    ],
  }
  ...
}

ztunnel 提供的 L4 遥测

您可能会惊喜地发现 ztunnel 日志很容易理解。例如,您将在目标 ztunnel 上看到 HTTP Connect 请求,其中指示源 Pod IP(peer_ip)和目标 Pod IP。

2023-02-15T20:40:48.628251Z  INFO inbound{id=4399fa68cf25b8ebccd472d320ba733f peer_ip=10.244.2.5 peer_id=spiffe://cluster.local/ns/default/sa/sleep}: ztunnel::proxy::inbound: got CONNECT request to 10.244.2.8:5000

您可以通过访问提供完整 TCP 标准指标localhost:15020/metrics API 来查看工作负载的 L4 指标,这些指标与 sidecar 公开的指标具有相同的标签。例如

istio_tcp_connections_opened_total{
  reporter="source",
  source_workload="sleep",
  source_workload_namespace="default",
  source_principal="spiffe://cluster.local/ns/default/sa/sleep",
  destination_workload="helloworld-v1",
  destination_workload_namespace="default",
  destination_principal="spiffe://cluster.local/ns/default/sa/helloworld",
  request_protocol="tcp",
  connection_security_policy="mutual_tls"
  ...
} 1

如果您安装了 Prometheus 和 Kiali,则可以轻松地从 Kiali 的 UI 中查看这些指标。

Kiali dashboard - L4 telemetry provided by ztunnel
Kiali 仪表盘 - ztunnel 提供的 L4 遥测

总结

我们非常高兴新的 基于 Rust 的 ztunnel 比之前的基于 Envoy 的 ztunnel 更加简化、轻量级和高性能。借助专为基于 Rust 的 ztunnel 设计的工作负载 xDS,您不仅可以更容易地理解 xDS 配置,还可以大幅减少 Istiod 控制平面和 ztunnel 之间的网络流量和成本。随着 Istio ambient 现在已合并到上游主分支,您可以按照我们的 入门指南 尝试新的基于 Rust 的 ztunnel。

分享此文章