首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Linkerd 流量拆分方案

在 Linkerd 中,金丝雀发布是通过流量拆分来管理的,这项功能允许你根据可动态配置的权重,将请求分配给不同的 Kubernetes 服务对象。虽然流量分割可以适用于任意的 Service 对象,但一般情况下是将一个 Service 的传入流量分给不同版本的 Service。

流量分割功能是通过 Linkerd 的?TrafficSplit CRD?来控制的(TrafficSplit CRD?遵循服务网接口(SMI)中定义的规范,这是 Linkerd 实现的几个 SMI API 中的一个)。创建?TrafficSplit CRD?允许我们控制 Linkerd 如何将流量代理给?TrafficSplit?引用的服务。

TrafficSplit CRD?是根据 Kubernetes Service 对象编写的,TrafficSplit?描述了一个中心根或?apex?服务,流量被发送到该服务,以及一个或多个实际接收它的后端服务,与?TrafficSplit?中还指定的权重成比例。

另外需要注意,Kubernetes 的 Service 对象不一定有后台工作负载。虽然这对于普通服务来说很少见,但是我们会在?TrafficSplits?的?apex?服务中大量使用该功能,因为?TrafficSplit?会导致发往?apex?的流量实际发送到后端服务,所以?apex?实际上不需要拥有自己的 Deployment。

更新服务

接下来我们还是以?Emojivoto?应用为例来创建两个新的 Service 对象,apex?服务将没有关联的 Deployment 资源,第二项服务将是?Emojivoto?的 web 服务的一个更新版本,会在页面顶部添加一些文本信息。

创建这两个服务后,我们将创建一个?TrafficSplit?资源,该资源会将发送到?apex?服务的流量在 web 服务的原始版本和更新版本之间进行拆分。

更新版本的 web 服务资源对象如下所示:

代码语言:javascript
复制
#?web-svc-2.yaml
apiVersion:?v1
kind:?ServiceAccount
metadata:
??name:?web-2
??namespace:?emojivoto
---
apiVersion:?v1
kind:?Service
metadata:
??name:?web-svc-2
??namespace:?emojivoto
spec:
??ports:
????-?name:?http
??????port:?80
??????targetPort:?8080
??selector:
????app:?web-svc-2
??type:?ClusterIP
---
apiVersion:?apps/v1
kind:?Deployment
metadata:
??labels:
????app.kubernetes.io/name:?web-svc-2
????app.kubernetes.io/part-of:?emojivoto
????app.kubernetes.io/version:?linux-training-v2
??name:?web-svc-2
??namespace:?emojivoto
spec:
??selector:
????matchLabels:
??????app:?web-svc-2
??????version:?linux-training-v2
??template:
????metadata:
??????annotations:
????????linkerd.io/inject:?enabled?#?设置自动注入的注解
??????labels:
????????app:?web-svc-2
????????version:?linux-training-v2
????spec:
??????containers:
????????-?env:
????????????-?name:?WEB_PORT
??????????????value:?"8080"
????????????-?name:?EMOJISVC_HOST
??????????????value:?emoji-svc.emojivoto:8080
????????????-?name:?VOTINGSVC_HOST
??????????????value:?voting-svc.emojivoto:8080
????????????-?name:?INDEX_BUNDLE
??????????????value:?dist/index_bundle.js
????????????-?name:?MESSAGE_OF_THE_DAY
??????????????value:?"Welcome?to?version?2!?Now?with?more?words!"
??????????image:?buoyantio/emojivoto-web:lf-training
??????????name:?web-svc-2
??????????ports:
????????????-?containerPort:?8080
??????????????name:?http
??????????resources:
????????????requests:
??????????????cpu:?100m
??????serviceAccountName:?web-2

直接应用上面的资源对象:

代码语言:javascript
复制
$?kubectl?apply?-f?web-svc-2.yaml
serviceaccount/web-2?created
service/web-svc-2?created
deployment.apps/web-svc-2?created

部署后先验证更新版本的服务是否已经正确部署了。

代码语言:javascript
复制
$?kubectl?get?po?--selector?app=web-svc-2?-n?emojivoto
NAME?????????????????????????READY???STATUS????RESTARTS???AGE
web-svc-2-f9d77474f-hgsg4????2/2?????Running???????0??????10s
$?kubectl?get?svc?web-svc-2?-n?emojivoto
NAME????????TYPE????????CLUSTER-IP??????EXTERNAL-IP???PORT(S)???AGE
web-svc-2???ClusterIP???10.102.99.153???<none>????????80/TCP????3m45s

部署成功后同样可以使用?kubectl port-forward?命令来暴露服务:

代码语言:javascript
复制
$?kubectl?port-forward?svc/web-svc-2?8080:80?-n?emojivoto

同样我们还是可以在浏览器中通过?localhost:8080?访问新版本的应用。

在页面顶部可以看到新版本的应用多了一行字符信息。

创建 TrafficSplit

接下来我们需要创建一个?apex?服务,我们这里将其称为?web-apex,不过这次没有 Pod 运行,所以无法向服务发送任何请求,因为没有端点。

代码语言:javascript
复制
#?web-apex.yaml
apiVersion:?v1
kind:?ServiceAccount
metadata:
??name:?web-apex
??namespace:?emojivoto
---
apiVersion:?v1
kind:?Service
metadata:
??name:?web-apex
??namespace:?emojivoto
spec:
??ports:
????-?name:?http
??????port:?80
??selector:
????app:?web-apex
??type:?ClusterIP

同样直接应用上面的资源对象:

代码语言:javascript
复制
$?kubectl?apply?-f?web-apex.yaml
serviceaccount/web-apex?created
service/web-apex?created
$?kubectl?get?svc?-n?emojivoto?-o?wide
NAME?????????TYPE????????CLUSTER-IP???????EXTERNAL-IP???PORT(S)?????????????AGE???SELECTOR
emoji-svc????ClusterIP???10.103.235.14????<none>????????8080/TCP,8801/TCP???8d????app=emoji-svc
voting-svc???ClusterIP???10.102.32.81?????<none>????????8080/TCP,8801/TCP???8d????app=voting-svc
web-apex?????ClusterIP???10.104.12.249????<none>????????80/TCP??????????????84s???app=web-apex
web-svc??????ClusterIP???10.106.220.250???<none>????????80/TCP??????????????8d????app=web-svc
web-svc-2????ClusterIP???10.102.99.153????<none>????????80/TCP??????????????27m???app=web-svc-2

从上面的输出可以看到?web-apex?服务和其他普通服务一样,但是他并没有端点。

代码语言:javascript
复制
$?kubectl?get?ep?-n?emojivoto
NAME?????????ENDPOINTS?????????????????????????????AGE
emoji-svc????10.244.1.228:8801,10.244.1.228:8080???8d
voting-svc???10.244.1.202:8801,10.244.1.202:8080???8d
web-apex?????<none>????????????????????????????????2m55s
web-svc??????10.244.1.227:8080?????????????????????8d
web-svc-2????10.244.1.233:8080?????????????????????28m

在继续之前我们可以先看看当前应用的流量状态:

代码语言:javascript
复制

$?linkerd?viz?stat?po?-n?emojivoto
NAME?????????????????????????STATUS???MESHED???SUCCESS??????RPS???LATENCY_P50???LATENCY_P95???LATENCY_P99???TCP_CONN
emoji-696d9d8f95-5vn9w??????Running??????1/1???100.00%???2.3rps???????????1ms???????????1ms???????????1ms??????????4
vote-bot-6d7677bb68-jvxsg???Running??????1/1???100.00%???0.3rps???????????1ms???????????1ms???????????1ms??????????1
voting-ff4c54b8d-xhjv7??????Running??????1/1????98.04%???0.8rps???????????1ms???????????8ms??????????10ms??????????4
web-5f86686c4d-58p7k????????Running??????1/1???100.00%???1.4rps???????????1ms???????????6ms???????????9ms??????????2

可以清楚看到虽然我们将 web 服务的更新版本已经部署了,但是现在没有产生任何的流量。接下来我们需要去创建一个?TrafficSplit?对象,然后去拆分一部分流量到我们的新服务中去。

创建一个如下所示的?TrafficSplit?资源对象:

代码语言:javascript
复制
#?web-svc-ts.yml
apiVersion:?split.smi-spec.io/v1alpha2
kind:?TrafficSplit
metadata:
??name:?web-svc-ts
??namespace:?emojivoto
spec:
??#?客户端用于连接到目标应用程序的?root?服务
??service:?web-apex
??#?命名空间中的 Service 以及它们自己的 selectors、端点和配置。
??backends:?#?拆分的后端服务
????-?service:?web-svc
??????weight:?500?#?权重
????-?service:?web-svc-2
??????weight:?500

上面的资源对象中主要包括两个属性:

  • service:客户端用于连接到目标应用程序的 root 服务
  • backends:命名空间内的服务,具有自己的选择器、端点和配置(我们可以将这些服务看成服务)。 service:与可以处理请求的 Pod 关联地具体服务的名称。 weight:它与分配给服务的总流量的百分比相关。

现在我们来应用上面的资源对象:

代码语言:javascript
复制
$?kubectl?apply?-f?web-svc-ts.yaml
trafficsplit.split.smi-spec.io/web-svc-ts?created
$?kubectl?get?trafficsplit?-n?emojivoto
NAME?????????SERVICE
web-svc-ts???web-apex

创建完成后我们可以通过?linkerd viz stat?命令的一个?trafficsplit?子命令(可以缩写为?ts),来显示所有的流量拆分统计信息。

代码语言:javascript
复制
$?linkerd?viz?stat?ts?-n?emojivoto
Starting?in?2.12,?the?SMI?extension?will?be?required?for?traffic?splitting.?Please?follow?the?SMI?extension?getting?started?guide?from?https://linkerd.io/2.10/tasks/linkerd-smi

NAME?????????APEX???????LEAF????????WEIGHT???SUCCESS???RPS???LATENCY_P50???LATENCY_P95???LATENCY_P99
web-svc-ts???web-apex???web-svc????????500?????????-?????-?????????????-?????????????-?????????????-
web-svc-ts???web-apex???web-svc-2??????500?????????-?????-?????????????-?????????????-?????????????-

由于投票机器人配置为将流量发送到?web-svc.emojivoto:80,所以现在我们看不到任何流量拆分的指标。所以我们先更新下?vote-bot?服务将流量发送到?web-apex?服务,而不是?web-svc。以下 kubectl 命令中使用的文件更改了 vote-bot 部署中的 WEB_HOST 环境变量,以将流量发送到 web-apex 服务,从而使 TrafficSplit 配置生效。

代码语言:javascript
复制
$?kubectl?edit?deploy?vote-bot?-n?emojivoto
#?......
????spec:
??????containers:
??????-?command:
????????-?emojivoto-vote-bot
????????env:
????????-?name:?WEB_HOST
??????????value:?web-apex.emojivoto:80
????????image:?docker.l5d.io/buoyantio/emojivoto-web:v11
????????imagePullPolicy:?IfNotPresent
????????name:?vote-bot
#?......

更新后新的?vote-bot?服务将向?web-apex?服务发出请求,我们可以使用上面的?trafficsplit?子命令再次来验证:

代码语言:javascript
复制
$?linkerd?viz?stat?ts?-n?emojivoto
Starting?in?2.12,?the?SMI?extension?will?be?required?for?traffic?splitting.?Please?follow?the?SMI?extension?getting?started?guide?from?https://linkerd.io/2.10/tasks/linkerd-smi

NAME?????????APEX???????LEAF????????WEIGHT???SUCCESS??????RPS???LATENCY_P50???LATENCY_P95???LATENCY_P99
web-svc-ts???web-apex???web-svc????????500????90.32%???1.0rps???????????3ms???????????9ms??????????10ms
web-svc-ts???web-apex???web-svc-2??????500????96.49%???0.9rps???????????1ms???????????5ms???????????5ms

从上面的输出可以看到?web-apex?服务是?web-svc?和?web-svc-2?服务的?APEX?服务,它们自己则是?LEAF?服务,输出结果还显示了每个服务的权重分布。

调整权重

接着我们再用?linkerd viz stat?命令来查看下应用的流量情况,上一次我们查看的时候?web-svc-2?服务关联的 Pod 没有收到任何流量。

代码语言:javascript
复制
$?linkerd?viz?stat?pod?-n?emojivoto
NAME?????????????????????????STATUS???MESHED???SUCCESS??????RPS???LATENCY_P50???LATENCY_P95???LATENCY_P99???TCP_CONN
emoji-696d9d8f95-5vn9w??????Running??????1/1???100.00%???2.3rps???????????1ms???????????2ms???????????3ms??????????5
vote-bot-646b9fd6fd-js526???Running??????1/1???100.00%???0.3rps???????????1ms???????????1ms???????????1ms??????????1
voting-ff4c54b8d-xhjv7??????Running??????1/1????89.74%???1.3rps???????????1ms???????????7ms???????????9ms??????????5
web-5f86686c4d-58p7k????????Running??????1/1????97.33%???1.2rps???????????2ms???????????9ms??????????10ms??????????3
web-svc-2-f9d77474f-hgsg4???Running??????1/1????92.31%???1.3rps???????????1ms???????????6ms???????????9ms??????????3

这次我们可以看到与?web-svc?和?web-svc-2?相关的两个 Pod 都在处理请求了。证明我们的流量拆分配置是正确的。

TrafficSplit?定义中将每个服务的权重设置为 500,以平均分配流量。在实际工作中,我们可以先将?web-svc-2?的权重设置为 1%的或者很低的权重开始,以确保没有错误,然后当我们确定新版本没有问题后,可以调整慢慢调整每个服务的权重,到最终所有流量都切换到新版本上面去。

我们可以通过手动编辑?TrafficSplit?对象来手动调整这两个服务的权重。将 75% 的流量发送到?web-svc-2,将?25%?的流量发送到?web-svc

代码语言:javascript
复制
#?web-svc-ts-2.yml
apiVersion:?split.smi-spec.io/v1alpha2
kind:?TrafficSplit
metadata:
??name:?web-svc-ts
??namespace:?emojivoto
spec:
??#?客户端用于连接到目标应用程序的?root?服务
??service:?web-apex
??#?命名空间中的 Service 以及它们自己的 selectors、端点和配置。
??backends:?#?拆分的后端服务
????-?service:?web-svc
??????weight:?250?#?权重
????-?service:?web-svc-2
??????weight:?750

更新上面的资源对象后,再次查看流量拆分的情况。

代码语言:javascript
复制
$?linkerd?viz?stat?ts?-n?emojivoto
Starting?in?2.12,?the?SMI?extension?will?be?required?for?traffic?splitting.?Please?follow?the?SMI?extension?getting?started?guide?from?https://linkerd.io/2.10/tasks/linkerd-smi

NAME?????????APEX???????LEAF????????WEIGHT???SUCCESS??????RPS???LATENCY_P50???LATENCY_P95???LATENCY_P99
web-svc-ts???web-apex???web-svc????????250????87.88%???0.6rps???????????6ms??????????17ms??????????20ms
web-svc-ts???web-apex???web-svc-2??????750????94.12%???1.4rps???????????2ms???????????8ms??????????10ms

在输出中,你将看到?WEIGHT?列与上面的变更相匹配,web-svc-2?为 750、web-svc?为 250。接着我们再更改?TrafficSplit?对象,将所有流量发送到?web-svc-2?服务去。

代码语言:javascript
复制
#?web-svc-ts-3.yml
apiVersion:?split.smi-spec.io/v1alpha2
kind:?TrafficSplit
metadata:
??name:?web-svc-ts
??namespace:?emojivoto
spec:
??#?客户端用于连接到目标应用程序的?root?服务
??service:?web-apex
??#?命名空间中的 Service 以及它们自己的 selectors、端点和配置。
??backends:?#?拆分的后端服务
????-?service:?web-svc
??????weight:?0?#?权重
????-?service:?web-svc-2
??????weight:?1

更新后我们再次查看流量拆分情况:

代码语言:javascript
复制
$?linkerd?viz?stat?ts?-n?emojivoto
Starting?in?2.12,?the?SMI?extension?will?be?required?for?traffic?splitting.?Please?follow?the?SMI?extension?getting?started?guide?from?https://linkerd.io/2.10/tasks/linkerd-smi

NAME?????????APEX???????LEAF????????WEIGHT???SUCCESS??????RPS???LATENCY_P50???LATENCY_P95???LATENCY_P99
web-svc-ts???web-apex???web-svc??????????0?????????-????????-?????????????-?????????????-?????????????-
web-svc-ts???web-apex???web-svc-2????????1????99.15%???1.9rps???????????2ms???????????3ms???????????3ms

正常我们可以看到?web-svc-2?服务对应的?WEIGHT?为 1,而?web-svc?为 0。

到这里我们就了解了 Linkerd 中的流量拆分的使用,为了简单起见,我们这里使用的是一个单独的?web-apex?服务,当然?apex?服务也可以是后端之一的服务,apex?和后端之一具有相同服务的?TrafficSplit?会将发往该服务的流量发送到该服务,但会与其余后端服务成比例,这是可以动态完成的,允许你在现有服务之上插入一个?TrafficSplit

例如,我们可以简单地使用?web-svc?作为?apex(并继续使用它以及?web-svc-2?作为后端),而不是使用?web-apex。在创建?TrafficSplit?的那一刻,到?web-svc?的现有流量将遵循?TrafficSplit?的规则;并且在它被删除的那一刻,到 web-svc 的流量将恢复正常。

在实践中我们往往还会将 Linkerd 的流量拆分功能与 CI/CD 系统进行集成,以自动化发布过程,Linkerd 本身就提供了相关指标,这结合起来是不是就可以实现渐进式交付了:通过将指标和流量拆分捆绑在一起,可以以增量、安全和完全自动化的方式发布新代码,前面我们介绍过 Argo Rollouts,我们也可以使用像?https://flagger.app/?这样的项目,因为它是建立在 Linkerd 的指标和流量拆分功能之上来执行渐进式交付的。

  • 发表于:
  • 本文为 InfoQ 中文站特供稿件
  • 首发地址https://www.infoq.cn/article/6c8456806d39074999ffe3ef8
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券
http://www.vxiaotou.com