13. 使用每个路由指标调试 HTTP 应用程序

这个 demo 是一个 Ruby 应用程序,可以帮助您管理书架。 它由多个微服务组成,并通过 HTTP 使用 JSON 与其他服务通信。有个服务:

  • webapp: 前端

  • authors: 用于管理系统中作者API

  • books: 用于管理系统中书籍API

出于演示目的,该应用程序带有一个简单的流量生成器。整体拓扑如下所示:

Topology
Topology

前提条件

要使用本指南,您需要在集群上安装 Linkerd 及其 Viz 扩展。 如果您还没有这样做,请按照安装 Linkerd 指南进行操作。

安装 App

首先,让我们将 books app 安装到您的集群上。在本地终端中,运行:

kubectl create ns booksapp && \
  curl -sL https://run.linkerd.io/booksapp.yml \
  | kubectl -n booksapp apply -f -

此命令为 demo 创建一个 namespace,下载其 Kubernetes 资源清单 并使用 kubectl 将其应用到您的集群。 该应用程序包含在 booksapp 命名空间中 运行的 Kubernetes 部署和服务。

第一次下载一堆容器需要一点时间。 Kubernetes 可以告诉您所有服务何时都在运行并准备好迎接流量。 通过运行以下命令等待它发生:

kubectl -n booksapp rollout status deploy webapp

您还可以通过运行以下命令快速查看添加到集群中的所有组件:

kubectl -n booksapp get all

部署成功完成后,您可以通过本地端口转发 webapp 访问应用程序本身:

kubectl -n booksapp port-forward svc/webapp 7000 &

在浏览器中打开 http://localhost:7000/ 以查看前端。

Frontend
Frontend

不幸的是,应用程序中有一个错误:如果您单击 Add Book,它有 50% 的时间会失败。 这是一个典型的不明显间歇性故障的案例——这种故障让服务所有者抓狂,因为它很难调试。 Kubernetes 本身无法检测或显示此错误。从 Kubernetes 的角度来看, 看起来一切都很好,但您知道应用程序正在返回错误。

Failure
Failure

将 Linkerd 添加到服务中

现在我们需要将 Linkerd 数据平面代理添加到服务中。最简单的选择是做这样的事情:

kubectl get -n booksapp deploy -o yaml \
  | linkerd inject - \
  | kubectl apply -f -

此命令检索 booksapp 命名空间中所有部署的清单, 通过 linkerd inject 运行它们,然后使用 kubectl apply 重新应用。 linkerd inject 命令对每个资源进行注解, 以指定它们应该添加 Linkerd 数据平面代理, 当清单重新应用于集群时,Kubernetes 会执行此操作。 最重要的是,由于 Kubernetes 进行滚动部署,因此应用程序始终保持运行。 (有关其工作原理的更多详细信息,请参阅自动代理注入。)

调试

让我们使用 Linkerd 来发现此应用程序失败的根本原因。 要查看 Linkerd dashboard,请运行:

linkerd viz dashboard &
Dashboard
Dashboard

从命名空间下拉列表中选择 booksapp 并单击 Deployments 工作负载。 您应该会看到 booksapp 命名空间中的所有部署都显示出来了。 会有成功率每秒请求数延迟百分位数

这很酷,但你会注意到 webapp成功率不是 100%。 这是因为流量生成器正在提交新书。 你可以自己做同样的事情,把成功率推得更低。 单击 Linkerd 仪表板中的 webapp 以进行实时调试会话

您现在应该查看 webapp 服务的详细信息视图。 您会看到 webapp 正在从 traffic(负载生成器)中获取流量, 并且它有两个传出依赖项:authorsbook。 一个是作者信息拉取服务,一个是图书信息拉取服务。

Detail
Detail

依赖服务中的故障可能正是导致 webapp 返回错误的原因(以及您作为用户在单击时可以看到的错误)。 我们可以看到 books 服务也故障了。让我们进一步向下滚动页面, 我们将看到 webapp 正在接收的所有流量端点的实时列表。这是有趣的:

Top
Top

啊哈!我们可以看到,从 webapp 服务到 books 服务的入站流量在很大一部分时间都失败了。 这可以解释为什么 webapp 会引发间歇性故障。让我们点击 tap (🔬)图标, 然后点击开始按钮来查看实际的请求和响应流。

Tap
Tap

事实上,许多这些请求都返回 500。

诊断仅影响单一路线的间歇性问题非常容易。 您现在拥有了打开详细错误报告所需的一切,该报告准确地解释了根本原因是什么。 如果 books 服务是您自己的,您就知道在代码中的确切位置。

服务配置文件

为了了解根本原因,我们使用了实时流量。 对于某些问题,这很好,但是如果问题是间歇性的并且发生在半夜会怎样? 服务配置文件Linkerd 提供了有关您的服务的一些附加信息。 这些定义了您正在服务的路由,除其他外,还允许在每个路由的基础上收集指标。 通过 Prometheus 存储这些指标,您将能够睡个好觉并在早上查找间歇性问题。

获取服务配置文件设置的最简单方法之一是使用现有的 OpenAPI (Swagger) 规范。 此 demo 已发布其每项服务的规范。您可以通过运行以下命令为 webapp 创建服务配置文件:

curl -sL https://run.linkerd.io/booksapp/webapp.swagger \
  | linkerd -n booksapp profile --open-api - webapp \
  | kubectl -n booksapp apply -f -

这个命令会做三件事:

  1. 获取 webapp 的 swagger 规范。
  2. 获取规范并使用 profile 命令将其转换为服务配置文件
  3. 将此配置应用于集群。

除了 installinjectprofile 也是一个纯文本操作。查看生成的配置文件:

apiVersion: linkerd.io/v1alpha2
kind: ServiceProfile
metadata:
  creationTimestamp: null
  name: webapp.booksapp.svc.cluster.local
  namespace: booksapp
spec:
  routes:
  - condition:
      method: GET
      pathRegex: /
    name: GET /
  - condition:
      method: POST
      pathRegex: /authors
    name: POST /authors
  - condition:
      method: GET
      pathRegex: /authors/[^/]*
    name: GET /authors/{id}
  - condition:
      method: POST
      pathRegex: /authors/[^/]*/delete
    name: POST /authors/{id}/delete
  - condition:
      method: POST
      pathRegex: /authors/[^/]*/edit
    name: POST /authors/{id}/edit
  - condition:
      method: POST
      pathRegex: /books
    name: POST /books
  - condition:
      method: GET
      pathRegex: /books/[^/]*
    name: GET /books/{id}
  - condition:
      method: POST
      pathRegex: /books/[^/]*/delete
    name: POST /books/{id}/delete
  - condition:
      method: POST
      pathRegex: /books/[^/]*/edit
    name: POST /books/{id}/edit

name 指的是你的 Kubernetes 服务的 FQDN, 在这个实例中 webapp.booksapp.svc.cluster.localLinkerd 使用请求的 Host header服务配置文件与请求相关联。 当代理看到 webapp.booksapp.svc.cluster.localHost header 时, 它将使用它来查找服务配置文件的配置。

路由是包含 method(例如 GET)和匹配路径的正则表达式的简单条件。 这允许您将 REST 风格的资源组合在一起,而不是看到一个巨大的列表。 路由的名称可以是您喜欢的任何名称。对于此 demo,该方法附加到路由正则表达式

要获取 authorsbooks 的配置文件,您可以运行:

curl -sL https://run.linkerd.io/booksapp/authors.swagger \
  | linkerd -n booksapp profile --open-api - authors \
  | kubectl -n booksapp apply -f -
curl -sL https://run.linkerd.io/booksapp/books.swagger \
  | linkerd -n booksapp profile --open-api - books \
  | kubectl -n booksapp apply -f -

当您使用 linkerd viz tap 时,很容易验证这一切是否有效。 每个实时请求都会显示正在看到的 :authorityHost header以及正在使用的 :pathrt_route。 运行以下命令:

linkerd viz tap -n booksapp deploy/webapp -o wide | grep req

这将观察流经 webapp 的所有实时请求,看起来像:

req id=0:1 proxy=in  src=10.1.3.76:57152 dst=10.1.3.74:7000 tls=true :method=POST :authority=webapp.default:7000 :path=/books/2878/edit src_res=deploy/traffic src_ns=booksapp dst_res=deploy/webapp dst_ns=booksapp rt_route=POST /books/{id}/edit

如你看到的:

  • :authority 是正确的 host
  • :path 正确匹配
  • rt_route 包含 route 名称

这些指标是 linkerd viz routes 命令的一部分, 而不是 linkerd viz stat。 要查看到目前为止累积的指标,请运行:

linkerd viz -n booksapp routes svc/webapp

这将输出所有观察到的路由及其黄金指标的表格。 [DEFAULT] 路由是所有与服务配置文件不匹配的所有内容。

配置文件可用于观察传出(outgoing)请求以及传入(incoming)请求。为此,请运行:

linkerd viz -n booksapp routes deploy/webapp --to svc/books

这将显示源自 webapp deployment 并发往 books 服务的所有请求和路由。 与在调试部分使用 taptop 视图类似,此 demo 中错误的根本原因很明显:

ROUTE                     SERVICE   SUCCESS      RPS   LATENCY_P50   LATENCY_P95   LATENCY_P99
DELETE /books/{id}.json     books   100.00%   0.5rps          18ms          29ms          30ms
GET /books.json             books   100.00%   1.1rps           7ms          12ms          18ms
GET /books/{id}.json        books   100.00%   2.5rps           6ms          10ms          10ms
POST /books.json            books    52.24%   2.2rps          23ms          34ms          39ms
PUT /books/{id}.json        books    41.98%   1.4rps          73ms          97ms          99ms
[DEFAULT]                   books         -        -             -             -             -

重试

由于更新代码推出新版本可能需要一段时间, 让我们告诉 Linkerd 它可以重试对失败端点的请求。这会 增加请求延迟,因为请求将被多次重试,并不需要推出新版本。

在这个应用中,从 books deployment 到 authors service 的请求成功率很低。 要查看这些指标,请运行:

linkerd viz -n booksapp routes deploy/books --to svc/authors

输出应如下所示:

ROUTE                       SERVICE   SUCCESS      RPS   LATENCY_P50   LATENCY_P95   LATENCY_P99
DELETE /authors/{id}.json   authors         -        -             -             -             -
GET /authors.json           authors         -        -             -             -             -
GET /authors/{id}.json      authors         -        -             -             -             -
HEAD /authors/{id}.json     authors    50.85%   3.9rps           5ms          10ms          17ms
POST /authors.json          authors         -        -             -             -             -
[DEFAULT]                   authors         -        -             -             -             -

有一点很清楚,从 booksauthors 的所有请求都发送到 HEAD /authors/{id}.json 路由, 并且这些请求在大约 50% 的时间内失败。

为了更正这个问题,让我们编辑 authors 服务配置文件并使那些请求可重试,运行以下命令:

kubectl -n booksapp edit sp/authors.booksapp.svc.cluster.local

您需要将 isRetryable 添加到特定路由。它应该看起来像:

spec:
  routes:
  - condition:
      method: HEAD
      pathRegex: /authors/[^/]*\.json
    name: HEAD /authors/{id}.json
    isRetryable: true ### ADD THIS LINE ###

编辑服务配置文件后,Linkerd 将开始自动重试对该路由的请求。我们可以看到通过运行以下命令:

linkerd viz -n booksapp routes deploy/books --to svc/authors -o wide

这应该是这样的:

ROUTE                       SERVICE   EFFECTIVE_SUCCESS   EFFECTIVE_RPS   ACTUAL_SUCCESS   ACTUAL_RPS   LATENCY_P50   LATENCY_P95   LATENCY_P99
DELETE /authors/{id}.json   authors                   -               -                -            -             -           0ms
GET /authors.json           authors                   -               -                -            -             -           0ms
GET /authors/{id}.json      authors                   -               -                -            -             -           0ms
HEAD /authors/{id}.json     authors             100.00%          2.8rps           58.45%       4.7rps           7ms          25ms          37ms
POST /authors.json          authors                   -               -                -            -             -           0ms
[DEFAULT]                   authors                   -               -                -            -             -           0ms

你会注意到 -o wide flag 在 routes 视图中添加了一些列。 这些显示了 EFFECTIVE_SUCCESSACTUAL_SUCCESS 之间的区别。 这两者之间的差异表明重试的效果如何。 EFFECTIVE_RPSACTUAL_RPS 显示有多少请求被发送到目标服务, 以及有多少请求被客户端的 Linkerd proxy 接收。

现在自动重试成功率看起来不错,但 p95p99 延迟增加了。这是可以预料的,因为重试需要时间。

超时

Linkerd 可以限制在传出请求到另一个服务失败之前等待的时间。 这些超时通过向服务配置文件的路由配置添加另一个 key 来工作。

首先,让我们看一下从 webappbooks 服务的请求的当前延迟:

linkerd viz -n booksapp routes deploy/webapp --to svc/books

这应该类似于:

ROUTE                     SERVICE   SUCCESS      RPS   LATENCY_P50   LATENCY_P95   LATENCY_P99
DELETE /books/{id}.json     books   100.00%   0.7rps          10ms          27ms          29ms
GET /books.json             books   100.00%   1.3rps           9ms          34ms          39ms
GET /books/{id}.json        books   100.00%   2.0rps           9ms          52ms          91ms
POST /books.json            books   100.00%   1.3rps          45ms         140ms         188ms
PUT /books/{id}.json        books   100.00%   0.7rps          80ms         170ms         194ms
[DEFAULT]                   books         -        -             -             -             -

books 服务的 PUT /books/{id}.json 路由的请求包括当该服务调用 authors 服务 作为服务这些请求的一部分时的重试,如上一节所述。这以额外的延迟为代价提高了成功率。 出于本 demo 的目的,让我们为对该路由的调用设置 25 毫秒超时。 您的延迟数字将根据集群的特征而有所不同。 要编辑 books 服务配置文件,请运行:

kubectl -n booksapp edit sp/books.booksapp.svc.cluster.local

更新 PUT /books/{id}.json 路由以设置超时:

spec:
  routes:
  - condition:
      method: PUT
      pathRegex: /books/[^/]*\.json
    name: PUT /books/{id}.json
    timeout: 25ms ### ADD THIS LINE ###

Linkerd 现在将在达到超时时将错误返回给 webapp REST 客户端。 此超时包括重试请求,并且是 REST 客户端等待响应的最长时间。

运行 routes 以查看发生了什么变化:

linkerd viz -n booksapp routes deploy/webapp --to svc/books -o wide

随着超时的发生,指标将发生变化:

ROUTE                     SERVICE   EFFECTIVE_SUCCESS   EFFECTIVE_RPS   ACTUAL_SUCCESS   ACTUAL_RPS   LATENCY_P50   LATENCY_P95   LATENCY_P99
DELETE /books/{id}.json     books             100.00%          0.7rps          100.00%       0.7rps           8ms          46ms          49ms
GET /books.json             books             100.00%          1.3rps          100.00%       1.3rps           9ms          33ms          39ms
GET /books/{id}.json        books             100.00%          2.2rps          100.00%       2.2rps           8ms          19ms          28ms
POST /books.json            books             100.00%          1.3rps          100.00%       1.3rps          27ms          81ms          96ms
PUT /books/{id}.json        books              86.96%          0.8rps          100.00%       0.7rps          75ms          98ms         100ms
[DEFAULT]                   books                   -               -                -            -             -

延迟数字包括在 webapp 应用程序本身中花费的时间, 因此预计它们会超过我们为从 webappbooks 的请求设置的 25 毫秒超时。 我们可以通过观察路由的有效成功率(effective success rate)下降到 100% 以下来看到超时正在起作用。

清理

要从集群中删除 books 应用程序和 booksapp 命名空间,请运行:

curl -sL https://run.linkerd.io/booksapp.yml \
  | kubectl -n booksapp delete -f - \
  && kubectl delete ns booksapp