王先森2024-01-112024-01-11
Linkerd 服务网格解决的最重要问题之一是可观察性:提供服务行为的详细视图,Linkerd 对可观察性的价值主张是,它可以为你的 HTTP 和 gRPC 服务提供黄金指标,这些都是自动执行,无需更改代码或开发人员参与的。
了解 Linkerd
如何为一项服务工作,安装一个简单的示例应用 Emojivoto
,该应用是一个简单的独立 Kubernetes
应用程序,它混合使用 gRPC
和 HTTP
调用,允许用户对他们最喜欢的表情符号进行投票。
通过运行以下命令可以将 Emojivoto
安装到 emojivoto
命名空间中:
$ 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
# 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
应用了。
如果你点击一个甜甜圈表情符号,你会得到一个 404
页面。别担心,这些错误是故意的。(我们可以使用 Linkerd
来识别问题)。
接下来我们可以将上面的示例应用加入到 Service Mesh
中来,向其添加 Linkerd 的数据平面代理,直接运行下面的命令即可将 Emojivoto
应用网格化:
$ 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
执行 滚动更新)以触发代理注入。
$ 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 的网格服务中来了,新增的代理容器组成了数据平面,我们也可以通过下面的命令检查数据平面状态:
$ linkerd -n emojivoto check --proxy
您现在可以查看 Linked
面板并查看 emojivoto
中的所有服务。由于 emojivoto
附带了 load generator
,我们可以通过运行以下命令查看实时流量指标(live traffic metrics
):
$ 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
来实时查看正在调用哪些路径:
linkerd -n emojivoto viz top deploy
为了更深入,我们可以使用 tap
显示跨单个 pod
、deployment
甚至 emojivoto
命名空间中的所有内容的请求流:
linkerd -n emojivoto viz tap deploy/web
Linkerd 在每个服务的基础上提供这些指标,跨越服务的所有请求,无论这些请求是什么。然而,有时需要获得更细粒度的指标。例如前面的 Emojivoto
应用程序中的 Emoji 微服务,前面看到的 Linkerd
报告的指标是在该服务的所有端点上聚合的。在实际场景下面,我们可能还希望看到特定端点的成功率或延迟,例如,一个端点可能对服务特别关键,或者特别慢。
为了解决这个问题,Linkerd 使用了服务配置文件(service profile
)的概念,服务配置文件是一个可选的配置,它通知 Linkerd 对服务的不同类型的请求,按其路由进行分类。路由只是一个端点(对于 gRPC
)或一个 HTTP verb 和端点(对于 HTTP
)。
服务配置文件允许 Linkerd 为服务提供每个路由(pre-route
)而不是每个服务指标。
Linkerd 的服务配置文件是通过实例化一个名为 ServiceProfile
的 Kubernetes CRD 来实现的。 ServiceProfile
会枚举 Linkerd 为该服务期望的路由。
可以手动创建 ServiceProfile
,但也可以自动生成它们。Linkerd CLI 有一个 profile
命令,可以通过几种不同的方式来生成服务配置文件。最常见的方法之一是从服务的现有资源(如 OpenAPI/Swagger
规范或 protobuf
文件)生成它们。
$ linkerd profile -h
上面的帮助命令输出列出了可用于为 ServiceProfile
资源生成 YAML 的标志,可以看到其中就有一个 --open-api
标志,用于指示 ServiceProfile
资源将从指定的 OpenAPI
或 Swagger
文档来生成服务配置文件,通过 --proto
标志可以指示从指定的 Protobuf
文件生成服务配置文件。
Emojivoto
的 web 服务有一个简单的 Swagger 规范文件,内容如下所示:
# vim web.swagger
openapi: 3.0.1
version: v10
paths:
/api/list:
get: {}
/api/vote:
get: {}
可以利用上面的规范文件来生成一个 ServiceProfile
对象,命令如下所示:
$ 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
用来声明所有的路由规则,每条路由中包含一个 name
和 condition
属性:
name
用来表示路由的名称,用于显示使用。condition
用来描述路由的规范。上例中生成的condition
有两个字段:method
:与请求匹配的 HTTP 方法。pathRegex
:用于匹配路径的正则表达式。在我们的示例中,这些是完全匹配的规则,但通常这些是正则表达式。除了通过 OpenAPI
可以生成服务配置文件之外,也可以通过 Protobuf
来生成,gRPC 协议使用 protobuf 对请求和响应进行编码和解码,这意味着每个 gRPC 服务也有一个 protobuf 定义。
Emojivoto
应用的 Voting 微服务就包含有 protobuf 定义,文件内容如下所示:
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
对象:
$ 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
功能一起使用,从实时流量中生成服务配置文件!如下命令所示:
$ 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
资源清单文件内容如下所示:
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
资源的脚手架,然后可以使用你的服务的详细信息对其进行更新。
$ linkerd profile --template -n emojivoto web
上面的命令会输出包含 ServiceProfile
资源的字段和每个字段的详细说明,如果你需要 ServiceProfile
定义的快速参考,就可以使用 --template
标志!
上面了解了如何使用 linkerd profile
命令来生成 ServiceProfile
资源清单文件,现在让我们重新运行生成命令,并直接将生成的 ServiceProfile
对象直接应用到集群中:
$ 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。
上面我们已经了解了如何通过 Dashboard 来查看 Emojivoto
应用中服务的每个路由指标了,接下来我们再尝试使用 CLI 工具查看每个路由的指标。
linkerd viz routes
命令使用与仪表盘使用的相同指标,让我们看看使用 Linkerd CLI 来查看 emoji
服务的路由,如下所示:
$ 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 服务的路由,如下所示:
$ 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
之类的工具来解析输出)。