Linkerd服务网格的基础知识和服务配置

Service Profiles

Linkerd 服务网格解决的最重要问题之一是可观察性:提供服务行为的详细视图,Linkerd 对可观察性的价值主张是,它可以为你的 HTTP 和 gRPC 服务提供黄金指标,这些都是自动执行,无需更改代码或开发人员参与的。

部署测试应用

了解 Linkerd 如何为一项服务工作,安装一个简单的示例应用 Emojivoto,该应用是一个简单的独立 Kubernetes 应用程序,它混合使用 gRPCHTTP 调用,允许用户对他们最喜欢的表情符号进行投票。

通过运行以下命令可以将 Emojivoto 安装到 emojivoto 命名空间中:

1
$ curl -fsL https://run.linkerd.io/emojivoto.yml | kubectl apply -f -

在我们对它进行网格(mesh)划分之前,让我们先来看看这个应用程序。可以通过 port-forward 来暴露 web-svc 服务kubectl -n emojivoto port-forward svc/web-svc 8080:80,也可以使用Ingress

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# vim emoji-ingress.yaml
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: emojivoto-web
namespace: emojivoto
spec:
entryPoints:
- web # secure
routes:
- match: Host(`emoji.od.com`) # 指定域名
kind: Rule
services:
- name: web-svc
port: 80

现在我们可以在浏览器通过 http://emoji.od.com 访问 Emojivoto 应用了。

Emojivoto

如果你点击一个甜甜圈表情符号,你会得到一个 404 页面。别担心,这些错误是故意的。(我们可以使用 Linkerd 来识别问题)。

接下来我们可以将上面的示例应用加入到 Service Mesh 中来,向其添加 Linkerd 的数据平面代理,直接运行下面的命令即可将 Emojivoto 应用网格化:

1
2
3
$ kubectl get -n emojivoto deploy -o yaml \
| linkerd inject - \
| kubectl apply -f -

此命令检索在 emojivoto 命名空间中运行的所有部署(Deployments),通过 linkerd inject 运行清单,然后将其重新应用到集群。linkerd inject 命令向 pod spec 添加注解(annotations),指示 Linkerd 将代理(proxy)作为容器添加(注入)到 pod spec 中。(有关更多信息,请参阅自动代理注入。)

将注释添加到现有 Pod 不会自动对它们进行网格划分。对于现有的 Pod,添加注释后,您还需要重新创建或更新资源(例如,通过使用kubectl rollout restart执行 滚动更新)以触发代理注入。

1
2
3
4
5
6
$ kubectl get pods -n emojivoto
NAME READY STATUS RESTARTS AGE
emoji-55c59cf485-zc46k 2/2 Running 0 176m
vote-bot-57d4c898bb-czzlr 2/2 Running 0 176m
voting-87469d4bb-sqc9n 2/2 Running 0 176m
web-847cbcb586-8c7mk 2/2 Running 0 176m

可以看到每个 Pod 现在都有 2 个容器,相较于之前多了一个 Linkerd 的 sidecar 代理容器。

当应用更新完成后,我们就成功将应用引入到 Linkerd 的网格服务中来了,新增的代理容器组成了数据平面,我们也可以通过下面的命令检查数据平面状态:

1
$ linkerd -n emojivoto check --proxy

监测它的运行

您现在可以查看 Linked 面板并查看 emojivoto 中的所有服务。由于 emojivoto 附带了 load generator,我们可以通过运行以下命令查看实时流量指标(live traffic metrics):

1
2
3
4
5
6
$ linkerd -n emojivoto viz stat deploy
NAME MESHED SUCCESS RPS LATENCY_P50 LATENCY_P95 LATENCY_P99 TCP_CONN
emoji 1/1 100.00% 2.2rps 1ms 1ms 1ms 4
vote-bot 1/1 100.00% 0.2rps 1ms 1ms 1ms 1
voting 1/1 78.38% 1.2rps 1ms 1ms 1ms 3
web 1/1 83.58% 2.2rps 2ms 4ms 4ms 3

这将显示每个部署的黄金(golden)指标

  • 成功率(Success rates)
  • 请求率(Request rates)
  • 延迟分布百分位数(Latency distribution percentiles)

为了进一步深入,可以使用 top 来实时查看正在调用哪些路径:

1
linkerd -n emojivoto viz top deploy

为了更深入,我们可以使用 tap 显示跨单个 poddeployment 甚至 emojivoto 命名空间中的所有内容的请求流:

1
linkerd -n emojivoto viz tap deploy/web

服务配置

Linkerd 在每个服务的基础上提供这些指标,跨越服务的所有请求,无论这些请求是什么。然而,有时需要获得更细粒度的指标。例如前面的 Emojivoto 应用程序中的 Emoji 微服务,前面看到的 Linkerd 报告的指标是在该服务的所有端点上聚合的。在实际场景下面,我们可能还希望看到特定端点的成功率或延迟,例如,一个端点可能对服务特别关键,或者特别慢。

为了解决这个问题,Linkerd 使用了服务配置文件(service profile)的概念,服务配置文件是一个可选的配置,它通知 Linkerd 对服务的不同类型的请求,按其路由进行分类。路由只是一个端点(对于 gRPC)或一个 HTTP verb 和端点(对于 HTTP)。

服务配置文件允许 Linkerd 为服务提供每个路由(pre-route)而不是每个服务指标。

生成服务配置文件

Linkerd 的服务配置文件是通过实例化一个名为 ServiceProfileKubernetes CRD 来实现的。 ServiceProfile 会枚举 Linkerd 为该服务期望的路由。

可以手动创建 ServiceProfile,但也可以自动生成它们。Linkerd CLI 有一个 profile 命令,可以通过几种不同的方式来生成服务配置文件。最常见的方法之一是从服务的现有资源(如 OpenAPI/Swagger 规范或 protobuf 文件)生成它们。

1
$ linkerd profile -h

上面的帮助命令输出列出了可用于为 ServiceProfile 资源生成 YAML 的标志,可以看到其中就有一个 --open-api 标志,用于指示 ServiceProfile 资源将从指定的 OpenAPISwagger 文档来生成服务配置文件,通过 --proto 标志可以指示从指定的 Protobuf 文件生成服务配置文件。

Emojivoto 的 web 服务有一个简单的 Swagger 规范文件,内容如下所示:

1
2
3
4
5
6
7
8
# vim web.swagger
openapi: 3.0.1
version: v10
paths:
/api/list:
get: {}
/api/vote:
get: {}

可以利用上面的规范文件来生成一个 ServiceProfile 对象,命令如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ linkerd profile -n emojivoto --open-api web.swagger web-svc > web-sp.yaml
$ cat web-sp.yaml
apiVersion: linkerd.io/v1alpha2
kind: ServiceProfile
metadata:
creationTimestamp: null
name: web-svc.emojivoto.svc.cluster.local
namespace: emojivoto
spec:
routes:
- condition:
method: GET
pathRegex: /api/list
name: GET /api/list
- condition:
method: GET
pathRegex: /api/vote
name: GET /api/vote

上面的资源清单文件就是一个典型的 ServiceProfile 对象的声明方式,spec.routes 用来声明所有的路由规则,每条路由中包含一个 namecondition 属性:

  • name 用来表示路由的名称,用于显示使用。
  • condition用来描述路由的规范。上例中生成的condition有两个字段:
    • method:与请求匹配的 HTTP 方法。
    • pathRegex:用于匹配路径的正则表达式。在我们的示例中,这些是完全匹配的规则,但通常这些是正则表达式。

除了通过 OpenAPI 可以生成服务配置文件之外,也可以通过 Protobuf 来生成,gRPC 协议使用 protobuf 对请求和响应进行编码和解码,这意味着每个 gRPC 服务也有一个 protobuf 定义。

Emojivoto 应用的 Voting 微服务就包含有 protobuf 定义,文件内容如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
syntax = "proto3";
option go_package = "github.com/buoyantio/emojivoto/proto";

package emojivoto.v1;

message VotingResult {
string Shortcode = 1;
int32 Votes = 2;
}

message VoteRequest {
}

message VoteResponse {
}

message ResultsRequest {
}

message ResultsResponse {
repeated VotingResult results = 1;
}

service VotingService {
rpc VotePoop (VoteRequest) returns (VoteResponse);
rpc VoteJoy (VoteRequest) returns (VoteResponse);
rpc VoteSunglasses (VoteRequest) returns (VoteResponse);
rpc VoteRelaxed (VoteRequest) returns (VoteResponse);
rpc VoteStuckOutTongueWinkingEye (VoteRequest) returns (VoteResponse);
rpc VoteMoneyMouthFace (VoteRequest) returns (VoteResponse);
rpc VoteFlushed (VoteRequest) returns (VoteResponse);
rpc VoteMask (VoteRequest) returns (VoteResponse);
rpc VoteNerdFace (VoteRequest) returns (VoteResponse);
rpc VoteGhost (VoteRequest) returns (VoteResponse);
rpc VoteSkullAndCrossbones (VoteRequest) returns (VoteResponse);
rpc VoteHeartEyesCat (VoteRequest) returns (VoteResponse);
rpc VoteHearNoEvil (VoteRequest) returns (VoteResponse);
rpc VoteSeeNoEvil (VoteRequest) returns (VoteResponse);
rpc VoteSpeakNoEvil (VoteRequest) returns (VoteResponse);
rpc VoteBoy (VoteRequest) returns (VoteResponse);
rpc VoteGirl (VoteRequest) returns (VoteResponse);
rpc VoteMan (VoteRequest) returns (VoteResponse);
rpc VoteWoman (VoteRequest) returns (VoteResponse);
rpc VoteOlderMan (VoteRequest) returns (VoteResponse);
rpc VotePoliceman (VoteRequest) returns (VoteResponse);
rpc VoteGuardsman (VoteRequest) returns (VoteResponse);
rpc VoteConstructionWorkerMan (VoteRequest) returns (VoteResponse);
rpc VotePrince (VoteRequest) returns (VoteResponse);
rpc VotePrincess (VoteRequest) returns (VoteResponse);
rpc VoteManInTuxedo (VoteRequest) returns (VoteResponse);
rpc VoteBrideWithVeil (VoteRequest) returns (VoteResponse);
rpc VoteMrsClaus (VoteRequest) returns (VoteResponse);
rpc VoteSanta (VoteRequest) returns (VoteResponse);
rpc VoteTurkey (VoteRequest) returns (VoteResponse);
rpc VoteRabbit (VoteRequest) returns (VoteResponse);
rpc VoteNoGoodWoman (VoteRequest) returns (VoteResponse);
rpc VoteOkWoman (VoteRequest) returns (VoteResponse);
rpc VoteRaisingHandWoman (VoteRequest) returns (VoteResponse);
rpc VoteBowingMan (VoteRequest) returns (VoteResponse);
rpc VoteManFacepalming (VoteRequest) returns (VoteResponse);
rpc VoteWomanShrugging (VoteRequest) returns (VoteResponse);
rpc VoteMassageWoman (VoteRequest) returns (VoteResponse);
rpc VoteWalkingMan (VoteRequest) returns (VoteResponse);
rpc VoteRunningMan (VoteRequest) returns (VoteResponse);
rpc VoteDancer (VoteRequest) returns (VoteResponse);
rpc VoteManDancing (VoteRequest) returns (VoteResponse);
rpc VoteDancingWomen (VoteRequest) returns (VoteResponse);
rpc VoteRainbow (VoteRequest) returns (VoteResponse);
rpc VoteSkier (VoteRequest) returns (VoteResponse);
rpc VoteGolfingMan (VoteRequest) returns (VoteResponse);
rpc VoteSurfingMan (VoteRequest) returns (VoteResponse);
rpc VoteBasketballMan (VoteRequest) returns (VoteResponse);
rpc VoteBikingMan (VoteRequest) returns (VoteResponse);
rpc VotePointUp2 (VoteRequest) returns (VoteResponse);
rpc VoteVulcanSalute (VoteRequest) returns (VoteResponse);
rpc VoteMetal (VoteRequest) returns (VoteResponse);
rpc VoteCallMeHand (VoteRequest) returns (VoteResponse);
rpc VoteThumbsup (VoteRequest) returns (VoteResponse);
rpc VoteWave (VoteRequest) returns (VoteResponse);
rpc VoteClap (VoteRequest) returns (VoteResponse);
rpc VoteRaisedHands (VoteRequest) returns (VoteResponse);
rpc VotePray (VoteRequest) returns (VoteResponse);
rpc VoteDog (VoteRequest) returns (VoteResponse);
rpc VoteCat2 (VoteRequest) returns (VoteResponse);
rpc VotePig (VoteRequest) returns (VoteResponse);
rpc VoteHatchingChick (VoteRequest) returns (VoteResponse);
rpc VoteSnail (VoteRequest) returns (VoteResponse);
rpc VoteBacon (VoteRequest) returns (VoteResponse);
rpc VotePizza (VoteRequest) returns (VoteResponse);
rpc VoteTaco (VoteRequest) returns (VoteResponse);
rpc VoteBurrito (VoteRequest) returns (VoteResponse);
rpc VoteRamen (VoteRequest) returns (VoteResponse);
rpc VoteDoughnut (VoteRequest) returns (VoteResponse);
rpc VoteChampagne (VoteRequest) returns (VoteResponse);
rpc VoteTropicalDrink (VoteRequest) returns (VoteResponse);
rpc VoteBeer (VoteRequest) returns (VoteResponse);
rpc VoteTumblerGlass (VoteRequest) returns (VoteResponse);
rpc VoteWorldMap (VoteRequest) returns (VoteResponse);
rpc VoteBeachUmbrella (VoteRequest) returns (VoteResponse);
rpc VoteMountainSnow (VoteRequest) returns (VoteResponse);
rpc VoteCamping (VoteRequest) returns (VoteResponse);
rpc VoteSteamLocomotive (VoteRequest) returns (VoteResponse);
rpc VoteFlightDeparture (VoteRequest) returns (VoteResponse);
rpc VoteRocket (VoteRequest) returns (VoteResponse);
rpc VoteStar2 (VoteRequest) returns (VoteResponse);
rpc VoteSunBehindSmallCloud (VoteRequest) returns (VoteResponse);
rpc VoteCloudWithRain (VoteRequest) returns (VoteResponse);
rpc VoteFire (VoteRequest) returns (VoteResponse);
rpc VoteJackOLantern (VoteRequest) returns (VoteResponse);
rpc VoteBalloon (VoteRequest) returns (VoteResponse);
rpc VoteTada (VoteRequest) returns (VoteResponse);
rpc VoteTrophy (VoteRequest) returns (VoteResponse);
rpc VoteIphone (VoteRequest) returns (VoteResponse);
rpc VotePager (VoteRequest) returns (VoteResponse);
rpc VoteFax (VoteRequest) returns (VoteResponse);
rpc VoteBulb (VoteRequest) returns (VoteResponse);
rpc VoteMoneyWithWings (VoteRequest) returns (VoteResponse);
rpc VoteCrystalBall (VoteRequest) returns (VoteResponse);
rpc VoteUnderage (VoteRequest) returns (VoteResponse);
rpc VoteInterrobang (VoteRequest) returns (VoteResponse);
rpc Vote100 (VoteRequest) returns (VoteResponse);
rpc VoteCheckeredFlag (VoteRequest) returns (VoteResponse);
rpc VoteCrossedSwords (VoteRequest) returns (VoteResponse);
rpc VoteFloppyDisk (VoteRequest) returns (VoteResponse);
rpc Results (ResultsRequest) returns (ResultsResponse);
}

同样可以使用 linkerd profile 命令来生成对应的 ServiceProfile 对象:

1
$ linkerd profile -n emojivoto --proto Voting.proto voting-svc > voting-sp.yaml

此命令的输出结果将比上一个命令的输出多很多,因为 Voting.proto 文件定义了更多路由,可以查看下 voting-sp.yaml 文件,并将其与上面创建的 ServiceProfile 资源进行比较。

此外还可以用另外一种方法来动态生成 ServiceProfile,Linkerd 可以监控在指定时间段内进入的实时请求,并从中收集路由数据。对于不了解服务提供的内部路由的集群管理员来说,这是一个非常强大的功能,因为 Linkerd 会负责处理请求并将它们写入文件,该特性的底层原理是实时观察请求的 Tap 功能。

现在,让我们使用 linkerd profile 命令监控 emoji 服务 10 秒并将输出重定向到文件,与前面学习的所有命令一样,输出会打印到终端,并且此命令会将输出重定向到文件,因此我们只需运行该命令(并等待 10 秒)一次。

Linkerd Viz 扩展也有自己的配置文件子命令,可以与 Tap 功能一起使用,从实时流量中生成服务配置文件!如下命令所示:

1
$ linkerd viz profile emoji-svc --tap deploy/emoji --tap-duration 10s -n emojivoto > auto-emoji-sp.yaml

当请求发送到 emoji 服务时,这些路由通过 Tap 功能收集。我们也可以使用 Emoji.proto 文件生成服务配置文件,然后去比较使用 --proto--tap 标志创建的 ServiceProfile 资源的定义。

生成的 ServiceProfile 资源清单文件内容如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
cat  auto-emoji-sp.yaml 
apiVersion: linkerd.io/v1alpha2
kind: ServiceProfile
metadata:
creationTimestamp: null
name: emoji-svc.emojivoto.svc.cluster.local
namespace: emojivoto
spec:
routes:
- condition:
method: POST
pathRegex: /emojivoto\.v1\.EmojiService/FindByShortcode
name: POST /emojivoto.v1.EmojiService/FindByShortcode
- condition:
method: POST
pathRegex: /emojivoto\.v1\.EmojiService/ListAll
name: POST /emojivoto.v1.EmojiService/ListAll

如果你需要手动编写服务配置文件,linkerd profile 命令有一个 --template 标志,可以生成 ServiceProfile 资源的脚手架,然后可以使用你的服务的详细信息对其进行更新。

1
$ linkerd profile --template -n emojivoto web

上面的命令会输出包含 ServiceProfile 资源的字段和每个字段的详细说明,如果你需要 ServiceProfile 定义的快速参考,就可以使用 --template 标志!

仪表板中查看Per-Route Metrics

上面了解了如何使用 linkerd profile 命令来生成 ServiceProfile 资源清单文件,现在让我们重新运行生成命令,并直接将生成的 ServiceProfile 对象直接应用到集群中:

1
2
3
4
5
6
$ linkerd profile -n emojivoto --open-api web.swagger web-svc | kubectl apply -f -
serviceprofile.linkerd.io/web-svc.emojivoto.svc.cluster.local created
$ linkerd profile -n emojivoto --proto Voting.proto voting-svc | kubectl apply -f -
serviceprofile.linkerd.io/voting-svc.emojivoto.svc.cluster.local created
$ linkerd viz profile emoji-svc --tap deploy/emoji --tap-duration 10s -n emojivoto | kubectl apply -f -
serviceprofile.linkerd.io/emoji-svc.emojivoto.svc.cluster.local created

当上面的命令运行成功后,让我们打开 Linkerd 仪表盘来查看下相关指标,我们先导航到 Web Deployment 下查看每个路由的指标。

从上图可以看出来在 ROUTE METRICS 选项卡下面相比默认的 [DEFAULT] 路由多了两个路由,这两个路由就是我们通过 linkerd profile --open-api 命令从 web.swagger 文件中生成的两条路由,每行列中,我们可以看到这两条路由的每条指标数据。在部署 ServiceProfile 对象之前,我们只能看到 web 服务的聚合指标,部署后我们现在可以看到 /api/list 这条路由是 100% 成功的,/api/vote 路由有一些错误。

同样在服务配置文件之前,我们只知道 web 服务正在返回错误,现在我们错误是来自与 /api/vote 路由,另外的 [DEFAULT] 默认路由表示当服务配置文件中没有路由匹配请求时 Linkerd 使用的路由,其会捕获在 ServiceProfile 之前观察到的任何流量。

现在我们再去看看另外的服务配置文件,其中的 Voting 服务比较具有代表性,因为它包含很多路由。在 Linkerd Dashboard 页面上在 emojivoto 命名空间上进入 Voting Deployment,切换到 ROUTE METRICS 选项卡。我们会该服务下有非常多的路由,上面的 web 服务我们知道 /api/vote 路由的请求成功率低于 100%,所有 voting 服务中的每条路由信息都有可能会提供相关的错误信息,由于路由非常多,我们可以直接按照 Success Rate 这一列进行升序排序,正常就可以看到 VoteDoughnut 路由成功率为 0。

命令行查看 Per-Route Metrics

上面我们已经了解了如何通过 Dashboard 来查看 Emojivoto 应用中服务的每个路由指标了,接下来我们再尝试使用 CLI工具 查看每个路由的指标。

linkerd viz routes 命令使用与仪表盘使用的相同指标,让我们看看使用 Linkerd CLI 来查看 emoji 服务的路由,如下所示:

1
2
3
4
5
$ linkerd viz routes deployment/web --namespace emojivoto   
ROUTE SERVICE SUCCESS RPS LATENCY_P50 LATENCY_P95 LATENCY_P99
GET /api/list web-svc 100.00% 1.0rps 1ms 3ms 5ms
GET /api/vote web-svc 83.33% 1.0rps 2ms 4ms 8ms
[DEFAULT] web-svc - - - - -

我们可以看到,为 emoji 服务定义的两条路由都是成功的,并且在 1ms 的时间内处理了请求,可以看出这些路由是健康的。还要注意我们的默认路由,标记为 [DEFAULT],同样这是 Linkerd 在服务配置文件中没有与请求匹配的路由时使用的路由。

现在我们用同样的方式通过 linkerd viz routes 命令来查看 voting 和 web 服务的路由,如下所示:

1
2
3
4
5
6
7
8
9
10
11
$ linkerd viz routes deployment/web --namespace emojivoto 
ROUTE SERVICE SUCCESS RPS LATENCY_P50 LATENCY_P95 LATENCY_P99
GET /api/list web-svc 100.00% 1.0rps 1ms 2ms 3ms
GET /api/vote web-svc 83.00% 1.0rps 2ms 3ms 4ms
[DEFAULT] web-svc - - - - -
$ linkerd viz routes deploy/voting -n emojivoto
VoteDancingWomen voting-svc 100.00% 0.0rps 1ms 1ms 1ms
VoteDog voting-svc - - - - -
VoteDoughnut voting-svc 0.00% 0.1rps 1ms 1ms 1ms
VoteFax voting-svc - - - - -
VoteFire voting-svc 100.00% 0.1rps 1ms 1ms 1ms

voting 服务的输出很长,因为它有很多路由,因此可以通过 grep 命令来查找 VoteDoughnut 路由:linkerd viz routes deploy/voting -n emojivoto | grep VoteDoughnut(或者可以使用 -o json 标志以及 jq 之类的工具来解析输出)。