一个开源 HTTP 路由器,以提高您的网络可见性

Skipper 提高了开发人员和运维人员的网络可观测性。
85 位读者喜欢这篇文章。
Teamwork starts with communication

图片来源:Mapbox Uncharted ERG, CC-BY 3.0 US

在我的上一篇文章中,我介绍了 Skipper,一个用于服务组合的开源 HTTP 路由器和反向代理。本文重点介绍 Skipper 如何提高网络可见性,并描述其对可扩展应用程序的开发人员和运行这些应用程序的基础设施的运维人员的优势。

什么是网络可见性?

什么是可见性,以及我们需要实现什么来支持它? Charity Majors 在 Twitter 上提供了一个很好的答案

作为 HTTP 路由提供商,Skipper 希望为应用程序开发人员提供可见性。在电子商务零售商 Zalando,我们特性团队的开发人员希望监控他们的系统,以了解失败率 (%)、吞吐量(每秒请求数或 RPS)和延迟(例如,p50、p99、p999)。想要检测其自定义代理的 Skipper 库用户可以使用 Skipper 的 metrics 包来检测他们的代理并创建自定义指标。

运维人员需要可观测性来了解系统在一般情况下和在特定时间点的行为方式,以便他们在有人问“昨天凌晨 2:30 发生了什么?”或“这个请求将如何处理?”时做好准备。Skipper 可以通过提高后端应用程序的可见性来帮助回答这些问题。

监控后端应用程序

就本文而言,我将后端应用程序定义为在 Skipper HTTP 代理后面运行的任意 HTTP 服务。

运行工作负载的应用程序所有者想要监控四个黄金信号:延迟、流量、错误和饱和度。像 Skipper 这样的 HTTP 路由器在调用路径中处于有利位置,可以为后端提供四个信号中的三个——延迟、流量和错误。但饱和度在实践中是一个复杂的目标;它必须在节点、控制组或应用程序级别进行监控,因此不在本文的讨论范围之内。

指标

Skipper 公开路由和聚合路由的响应延迟、RPS 吞吐量以及 OK 和失败率。这些是应用程序所有者需要获得警报的基本信号,以便他们可以根据需要采取行动。

Skipper 可以根据您的需求提供不同的应用程序指标。指标可以通过 Host 标头或 route 收集。如果您使用 -serve-host-metrics 启动 Skipper,您可以收集和公开按 Host 标头分组的指标。如果您需要更多详细信息,可以使用 -serve-route-metrics 按路由、状态代码和方法拆分指标。

请记住,没有什么是不需要成本的,拥有更多的指标并不总是更好。您将为在 Skipper 和您的时间序列数据库 (TSDB) 中存储和查询指标而付出内存使用量的代价。

您还可以选择以 Coda HalePrometheus 格式公开您的指标。 Coda Hale 指标以 pN(例如 p99)格式公开预聚合的延迟。这些预聚合指标的一个缺点是,您将无法获得跨所有实例的统计上正确的 p99。如果您想获得跨所有 Skipper 实例计算的正确的 pN,我建议使用 Prometheus 来抓取 Skipper 实例。然后,您可以查询 Prometheus 以计算延迟、错误和吞吐量。

延迟

要获取后端的响应延迟,您可以使用图 1 中所示的 PromQL 查询。此查询将获取按主机标头拆分的过去一分钟的 p99 延迟

图 1

histogram_quantile(
  0.99,
  sum(rate(skipper_serve_host_duration_seconds_bucket{}[1m]))
  by (le,host)
)

流量

要获取吞吐量数据,您可以使用图 2 中所示的查询。此 PromQL 查询显示按主机标头拆分的 RPS

图 2

sum(rate(skipper_serve_host_duration_seconds_count{}[1m])) by (host)

错误

您有多种选择来监控 Skipper 中的错误。要获取按主机标头划分的每秒 HTTP 状态代码 4xx 和 5xx 错误,请使用图 3 中所示的查询

图 3

sum(rate(skipper_serve_host_duration_seconds_count{code=~"[45].."}[1m]))
by (host)
/
sum(rate(skipper_serve_host_duration_seconds_count{}[1m]))
by (host)

如果您只想查看错误,请使用图 4 中的查询

图 4

sum(rate(skipper_serve_host_duration_seconds_count{code=~"[45].."}[10m])) by (host)

如果您想查看成功率百分比,请使用图 5 中的查询

图 5

(
  sum(rate(skipper_serve_host_duration_seconds_count{}[10m])) by (host)
  - sum(rate(skipper_serve_host_duration_seconds_count{code=~"[45].."}[10m])) by (host)
) / sum(rate(skipper_serve_host_duration_seconds_count{}[10m])) by (host)

访问日志

Skipper 还提供访问日志,这非常有价值。您可以使用 enableAccessLogdisableAccessLog 过滤器来控制各个路由的状态代码的日志输出。访问日志还包含您可以追溯到各个客户端的数据。例如,您可以使用 unverifiedAuditLog 过滤器来记录 JSON Web Token (JWT) 的部分内容,这些内容将识别客户端。这有助于应用程序所有者了解哪个客户端具有什么样的访问模式。

Skipper 支持 JSON 或 Apache 2 风格的访问日志格式的结构化日志。 Apache 格式适合人类阅读,并且有一个庞大的生态系统可以与之配合使用。如果您想用机器处理日志,您可能需要使用 JSON 格式。

通常,最终用户网站(如网上商店)使用多个应用程序或服务。对于调查,您希望查看单个用户事务的所有日志,以便您可以了解错误并在幕后修复问题。在您的所有应用程序的日志中加入业务跟踪 ID 怎么样?在 Zalando,我们使用 flowID 标识符实现了事件日志,该标识符作为 HTTP 标头传递。 Skipper 有一个 flowID 过滤器,也会将该值写入您的访问日志。这使您可以轻松找到一个业务事务的所有访问日志,而无需像分布式跟踪这样的较新工具包。

分布式跟踪

分布式跟踪在现代可见性工具包中非常重要。我们使用此术语来指代由 OpenTelemetry 提供的生态系统,以前称为 OpenTracingOpenCensus

分布式跟踪提供了分布式系统的完全不同的视图。例如,您可以获得如图 6 所示的瀑布图类型。它显示了多个服务执行不同的操作,所有操作都以持续时间来衡量。左侧的初始 HTTP GET / dispatch span 打开了另外两个 span:一个 HTTP GET /customer 调用和一个 Driver::findNearest Redis 操作。

图 6

这提供了大量的可见性!

您可以在下面找到有关 Skipper 的分布式跟踪支持的更多详细信息。

运维

分布式服务的运维人员希望了解边缘代理的运行情况。他们想知道 Skipper 是否健康,是否正在导致应用程序延迟,如果是,是什么消耗了时间。他们还想区分后端问题和 HTTP 路由器问题,以减少平均修复时间。

大多数应用程序都应提供核心指标,如内存和 CPU 使用率,但这对于如此重要的组件(作为整个业务的支柱)来说是不够的——后端延迟、RPS 吞吐量、错误率、连接状态、队列状态和重启也非常重要。 Skipper 是用 Go 编写的,它公开了一些有趣的 运行时指标,例如内存、垃圾回收详细信息、线程数和 goroutine 数。所有这些指标都在支持侦听器上导出,默认情况下为 :9911/metrics

图 7 显示了一个 Skipper 仪表板,其中包含一系列集群范围的图表。第一行显示吞吐量、后端延迟和有关进程并发性的数据。这些图表显示了重要的集群模式以及 HTTP 路由和后端是否健康。硬件更改可能会改变 Go 运行时使用的线程数或每个实例的计算吞吐量。在第二行中,您可以看到 HTTP 状态代码 2xx、4xx 和 5xx,以获得错误的总体视图并了解什么是被认为是“正常”的。

图 7

图 8 显示了其他运行时和容量详细信息。显示按主机标头拆分的请求的图表使您能够识别应用程序的访问模式。

图 8

图 9 的底部显示了两个不同容器的 CPU 使用率。顶部显示了过滤器和 LIFO(后进先出)队列,这需要更多解释。例如,如果用户开始使用 compress() 过滤器,您可能会看到响应过滤器变慢并且 CPU 使用率增加,因为现在您正在将 CPU 用于压缩算法,而不仅仅是移动数据包。如果开发人员开始使用调用外部 API(如 webhook()oauth*)的身份验证过滤器,则请求过滤器运行时也会计算到这些端点的往返行程。此信息显示观察到的应用程序延迟是否是更全局性的东西(如慢速 tokeninfo 端点)的副作用。图 9 显示在 17:00 有 tokeninfo CPU 使用率的新指标。我将 tokeninfo 身份验证端点部署为 Sidecar,这减少了过滤器延迟

以及 Skipper CPU 使用率。

图 9

分布式跟踪

如果您回顾 图 6 中的瀑布图,您可以看到整个应用程序的请求流。该信息由跟踪库提供。 Skipper 当前支持跟踪提供商 InstanaJaegerLightstep,因此您可以选择使用 SaaS 提供商或开源版本。

您的分布式跟踪提供商提供的跟踪很好,但如果您深入了解细节,您可以获得比仅从瀑布图更多的信息。

在分布式跟踪中,跟踪由连接的 span 组成。Span 具有服务、操作和持续时间,但它们也可以包含元数据,如标签和日志。使用 Skipper,您可以设置进程全局标签,例如环境、主机名、集群名称或云提供商。这对于过滤和分组您的跟踪很有用。

Skipper 为每个操作提供五个 span

  • Ingress: 父 span,HTTP 处理程序,包括路由查找
  • 请求过滤器: 处理请求过滤器
  • 身份验证过滤器: 与您的授权提供商交互
  • 代理: 包装后端调用
  • 响应过滤器: 处理响应过滤器

例如,在图 10 中的 请求过滤器 span 中,您可以看到在日志中运行路由的所有单个过滤器所花费的时间。

图 10

代理 span 中,日志显示 dial、TCP/IP 连接和 TLS 握手、到后端的 HTTP 往返以及将标头和数据流式传输到客户端所花费的时间。即使没有检测后端,正如您在图 11 中看到的那样,后端大约花费了 90 毫秒,而 dial_context、TCP/IP 和 TLS 握手花费了 1.04 毫秒。

图 11

如果您需要检测您的 Go 应用程序,您可以使用 Skipper HTTP 客户端,它在客户端 span 中提供详细的日志。例如,图 12 显示了 tokeninfo 和身份验证过滤器 span,它们正在使用 Client

图 12

更多详细信息可以在 Skipper 的 OpenTracing 文档中找到。

路由表

与任何其他 HTTP 路由器一样,Skipper 具有路由表,或者更准确地说,是路由树(如我的上一篇文章中所述)。使用树结构允许您扩展路由数量并在查找期间获得良好的性能。这使 Skipper 可以在一个实例中运行超过 600,000 条路由。

作为运维人员,您有时必须调查路由问题。如果不必处理配置源,而是拥有路由树的在线视图,那就太好了。借助 -support-listener,Skipper 使您能够转储实例的当前路由表。图 13 显示了如何转储路由表的部分内容。在本例中,使用 offset= 10limit=1 将仅打印第十条路由

图 13

% curl "localhost:9911/routes?offset=10&limit=1"
kube_default__foo__foo_teapot_example_org_____foo: Host(/^foo[.]teapot[.]example[.]org$/) && PathSubtree("/")
  -> enableAccessLog(4, 5)
  -> lifo(2000, 20000, "3s")
  -> setRequestHeader("X-Foo", "hello-world")
  -> <roundRobin, "http://10.2.0.225:9090", "http://10.2.1.244:9090">;

调试请求

我的上一篇文章表明,Skipper 过滤器可以更改传递给后端的请求。但是,您如何调查传出的请求?人们通常尝试使用调试日志或 SSH 进入机器并使用 tcpdump 转储所有内容以查找传出的请求。

Skipper 提供了一种更好的方法来检查转换后的请求。通过使用调试侦听器 -debug-listener=:9922,Skipper 可以显示有关传入和传出请求的信息。在图 14 中,您可以看到 outgoing 请求应用了 X-Foo: hello-world 请求标头,该标头由图 13 中的过滤器 setRequestHeader("X-Foo", "hello-world") 添加。

图 14

% curl -s http://127.0.0.1:9922/ -H"Host: foo.teapot.example.org" | jq .
{
  "route_id": "kube_default__foo__foo_teapot_example_org_____foo",
  "route": "Host(/^foo[.]teapot[.]example[.]org$/) && PathSubtree(\"/\") -> enableAccessLog(4, 5) -> lifo(2000, 20000, \"3s\") -> setRequestHeader(\"X-Foo\", \"hello-world\") -> <roundRobin, \"http://10.2.0.225:9090\", \"http://10.2.1.244:9090\">",
  "incoming": {
    "method": "GET",
    "uri": "/",
    "proto": "HTTP/1.1",
    "header": {
      "Accept": [
        "*/*"
      ],
      "User-Agent": [
        "curl/7.49.0"
      ]
    },
    "host": "foo.teapot.example.org",
    "remote_address": "127.0.0.1:32992"
  },
  "outgoing": {
    "method": "GET",
    "uri": "",
    "proto": "HTTP/1.1",
    "header": {
      "Accept": [
        "*/*"
      ],
      "User-Agent": [
        "curl/7.49.0"
      ],
      "X-Foo": [
        "hello-world"
      ]
    },
    "host": "foo.teapot.example.org"
  },
  "response_mod": {
    "header": {
      "Server": [
        "Skipper"
      ]
    }
  },
  "filters": [
    {
      "name": "enableAccessLog",
      "args": [
        4,
        5
      ]
    },
    {
      "name": "lifo",
      "args": [
        2000,
        20000,
        "3s"
      ]
    },
    {
      "name": "setRequestHeader",
      "args": [
        "X-Foo",
        "hello-world"
      ]
    }
  ],
  "predicates": [
    {
      "name": "PathSubtree",
      "args": [
        "/"
      ]
    }
  ]
}

总结

本文介绍了 Skipper 如何使您能够提高 HTTP 路由系统的可见性。我希望这能为您提供提高可观测性所需的信息,但如果您有任何问题,请在下面的评论中分享它们。

接下来阅读什么
标签
Sandor
Sandor Szücs Sr. Zalando SE 开发者效率高级软件工程师 @sszuecs

评论已关闭。

知识共享许可协议本作品根据知识共享署名-相同方式共享 4.0 国际许可协议获得许可。
© . All rights reserved.