流量管理

在本节中,我们将延续上一个任务【在 Istio 环境部署 Dubbo-go 应用】

在之前的任务中,我们在集群中部署了一组 Dubbo-go Server和 Client 端应用,验证了服务发现和调用成功。在本节中,我们将创建新版本的 Server 端应用。通过配置 VirtualService 和 DestinationRule ,实现路由管理,和流量转移能力

1. 准备工作

2. 开发多版本Dubbo-go 应用。

2.1 使用 dubbogo-cli 创建另一个项目模板

  1. $ dubbogo-cli newApp .

2.2 开发和部署客户端 Dubbo-go 应用 v2:

编写业务逻辑

  • 修改 package/service/service.go 的实现方法,返回版本号为 v2.0.0
  1. func (s *GreeterServerImpl) SayHello(ctx context.Context, in *api.HelloRequest) (*api.User, error) {
  2. return &api.User{Name: "Hello " + in.Name, Id: "v2.0.0"}, nil
  3. }
  • 修改如下配置文件,使用xds协议作为注册中心,加载名为 GreeterServerImpl 的服务结构。

    conf/dubbogo.yaml

    1. dubbo:
    2. registries:
    3. xds:
    4. protocol: xds
    5. address: istiod.istio-system.svc.cluster.local:15010
    6. protocols:
    7. triple:
    8. name: tri
    9. port: 20000
    10. provider:
    11. services:
    12. GreeterServerImpl:
    13. interface: "" # read from stub

    至此,应用开发完成。

配置构建和部署参数

  • 指定需要构建的镜像:

    修改 Makefile 如下字段,指定好需要构建的镜像地址和版本,我们把镜像 tag 改为 2.0.0。

    指定好需要通过 helm 安装的名称。

    1. IMAGE = xxx/dubbo-go-server
    2. TAG = 2.0.0
    3. HELM_INSTALL_NAME = dubbo-go-server
  • 指定需要部署的应用和镜像:

    修改 chart/app/Chart.yaml 如下字段,指定当前应用名为 dubbo-go-server,我们在创建v1版本服务的时候,已经有了该应用的 service,这次部署时模板将不会创建 service。

    1. apiVersion: v1
    2. name: dubbo-go-server
    3. description: dubbo-go-server

    修改 chart/app/values.yaml 如下字段,指定需要部署的镜像为2.0.0,以及当前开发的应用版本 dubbogoAppVersion 为 v2。

    部署的镜像需要和上述构建的镜像一致。当前应用版本用于 mesh 流量规则控制。

    1. image:
    2. repository: xxx/dubbo-go-server
    3. pullPolicy: Always
    4. tag: "2.0.0"
    5. # Dubbo-go-mesh version control labels
    6. version:
    7. labels:
    8. dubbogoAppVersion: v2

    至此,构建参数和发布参数都已指定好,可以进行构建和部署了。

使用模板构建和部署 Dubbo-go 应用

  • 构建、推送镜像

    $ make build (本地为 amd64机器)

    或者

    $ make buildx-publish (本地为 arm64机器,依赖 docker buildx 命令)

  • 发布 Dubbo-go Server v2 至集群

    1. $ make deploy
    2. NAME: dubbo-go-server-v2
    3. LAST DEPLOYED: Thu Apr 7 12:29:28 2022
    4. NAMESPACE: default
    5. STATUS: deployed
    6. REVISION: 1
    7. TEST SUITE: None
    8. $ helm list
    9. NAME NAMESPACE REVISION UPDATED STATUS CHART dubbo-go-client default 1 2022-04-07 11:49:55.517898 +0800 CST deployed dubbo-go-client-0.0.1 1.16.0
    10. dubbo-go-server-v1 default 1 2022-04-07 11:23:18.397658 +0800 CST deployed dubbo-go-server-0.0.1 1.16.0
    11. dubbo-go-server-v2 default 1 2022-04-07 12:29:28.497476 +0800 CST deployed dubbo-go-client-0.0.1 1.16.0

    可看到通过 helm 部署成功, 目前已经在集群中存在一个 Client 应用,和 Server 的两个版本。

2.3 验证应用

查看资源部署情况

查看部署好的 deployment ,server 包含了两个版本。

  1. $ kubectl get deployment
  2. NAME READY UP-TO-DATE AVAILABLE AGE
  3. dubbo-go-client-v1 1/1 1 1 40m
  4. dubbo-go-server-v2 1/1 1 1 77s
  5. dubbo-go-server-v1 1/1 1 1 67m

查看部署好的 service。两个版本的deployment 共用同一个 service。

  1. $ kubectl get svc
  2. NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
  3. dubbo-go-client ClusterIP 192.168.8.176 <none> 20000/TCP 41m
  4. dubbo-go-server ClusterIP 192.168.216.253 <none> 20000/TCP 67m

查看 Client 应用日志,验证请求调用到了两个版本的应用上。

  1. $ kubectl get pods | grep client | awk '{print $1}' | xargs kubectl logs
  2. ...
  3. 2022-04-07T05:06:40.384Z INFO cmd/app.go:29 call server response = name:"Hello laurence" id:"v2.0.0"
  4. 2022-04-07T05:06:41.386Z INFO cmd/app.go:29 call server response = name:"Hello laurence" id:"v2.0.0"
  5. 2022-04-07T05:06:42.388Z INFO cmd/app.go:29 call server response = name:"Hello laurence" id:"v1.0.0"
  6. 2022-04-07T05:06:43.389Z INFO cmd/app.go:29 call server response = name:"Hello laurence" id:"v2.0.0"
  7. 2022-04-07T05:06:44.390Z INFO cmd/app.go:29 call server response = name:"Hello laurence" id:"v2.0.0"
  8. 2022-04-07T05:06:45.392Z INFO cmd/app.go:29 call server response = name:"Hello laurence" id:"v1.0.0"
  9. 2022-04-07T05:06:46.393Z INFO cmd/app.go:29 call server response = name:"Hello laurence" id:"v1.0.0"
  10. 2022-04-07T05:06:47.394Z INFO cmd/app.go:29 call server response = name:"Hello laurence" id:"v1.0.0"
  11. 2022-04-07T05:06:48.396Z INFO cmd/app.go:29 call server response = name:"Hello laurence" id:"v2.0.0"
  12. 2022-04-07T05:06:49.397Z INFO cmd/app.go:29 call server response = name:"Hello laurence" id:"v1.0.0"

至此,我们开发并且部署成功了多版本应用。

3. 配置请求路由

3.1 配置目标规则

执行以下命令以创建目标规则,该目标规则将 dubbo-go-server 细分为两个子集。v1和 v2

  1. $ kubectl apply -f destinationrule.yaml

destinationrule.yaml 内容:

  1. apiVersion: networking.istio.io/v1alpha3
  2. kind: DestinationRule
  3. metadata:
  4. name: dubbo-go-server
  5. spec:
  6. host: dubbo-go-server
  7. subsets:
  8. - name: v1
  9. labels:
  10. dubbogoAppVersion: v1 # 对应应用模板中 chart/app/values.yaml 中指定的版本标签
  11. - name: v2
  12. labels:
  13. dubbogoAppVersion: v2

3.2 应用 Virtual Service

执行以下命令以创建路由,该路由将所有流量都路由至v1 版本应用。

  1. $ kubectl apply -f virtualservice.yaml

virtualservice.yaml 内容

  1. apiVersion: networking.istio.io/v1alpha3
  2. kind: VirtualService
  3. metadata:
  4. name: dubbo-go-server
  5. spec:
  6. hosts:
  7. - dubbo-go-server
  8. http:
  9. - route:
  10. - destination:
  11. host: dubbo-go-server
  12. subset: v1

3.3 验证路由生效

所有流量将流向 v1 版本应用。

  1. $ kubectl get pods | grep client | awk '{print $1}' | xargs kubectl logs
  2. ...
  3. 2022-04-07T05:40:44.353Z INFO cmd/app.go:29 call server response = name:"Hello laurence" id:"v1.0.0"
  4. 2022-04-07T05:40:45.354Z INFO cmd/app.go:29 call server response = name:"Hello laurence" id:"v1.0.0"
  5. 2022-04-07T05:40:46.356Z INFO cmd/app.go:29 call server response = name:"Hello laurence" id:"v1.0.0"
  6. 2022-04-07T05:40:47.357Z INFO cmd/app.go:29 call server response = name:"Hello laurence" id:"v1.0.0"
  7. 2022-04-07T05:40:48.359Z INFO cmd/app.go:29 call server response = name:"Hello laurence" id:"v1.0.0"
  8. 2022-04-07T05:40:49.361Z INFO cmd/app.go:29 call server response = name:"Hello laurence" id:"v1.0.0"

4. 基于用户身份的路由

有了上述多版本应用路由的基础,我们可以通过一些策略,来进行灵活的流量管理。

4.1 为客户端应用增加用户身份标识

我们希望拥有 user: admin 标识的流量都可以体验 v2 新版本应用。

回到之前创建的 dubbo-go-client 项目,修改 cmd/app.go 的 main 函数,增加调用标识:user: admin

  1. func main() {
  2. client := &api.GreeterClientImpl{}
  3. config.SetConsumerService(client)
  4. if err := config.Load(); err != nil {
  5. panic(err)
  6. }
  7. request := &api.HelloRequest{
  8. Name: "laurence",
  9. }
  10. for{
  11. ctx := context.Background()
  12. ctx = context.WithValue(ctx, constant.AttachmentKey, map[string]string{
  13. "user":"admin", // 使用上下文 context 为调用增加标识
  14. })
  15. if rsp, err := client.SayHello(ctx, request); err != nil{
  16. logger.Errorf("call server error = %s", err)
  17. }else{
  18. logger.Infof("call server response = %+v", rsp)
  19. }
  20. time.Sleep(time.Second)
  21. }
  22. }
  • 构建、推送镜像,覆盖之前的提交。您也可以升级一下发布的镜像版本。

    $ make build (本地为 amd64机器)

    或者

    $ make buildx-publish (本地为 arm64机器,依赖 docker buildx 命令)

  • 删除 dubbo-go-client 应用

    1. $ make remove
    2. helm uninstall dubbo-go-client
    3. release "dubbo-go-client" uninstalled
  • 重新发布应用。

    $ make deploy

    发布后,验证调用成功,由于前面进行了路由配置。所有流量都流向 v1 版本。

    1. $ kubectl get pods | grep client | awk '{print $1}' | xargs kubectl logs
    2. ...
    3. 2022-04-07T05:40:44.353Z INFO cmd/app.go:29 call server response = name:"Hello laurence" id:"v1.0.0"
    4. 2022-04-07T05:40:45.354Z INFO cmd/app.go:29 call server response = name:"Hello laurence" id:"v1.0.0"
    5. 2022-04-07T05:40:46.356Z INFO cmd/app.go:29 call server response = name:"Hello laurence" id:"v1.0.0"
    6. 2022-04-07T05:40:47.357Z INFO cmd/app.go:29 call server response = name:"Hello laurence" id:"v1.0.0"
    7. 2022-04-07T05:40:48.359Z INFO cmd/app.go:29 call server response = name:"Hello laurence" id:"v1.0.0"
    8. 2022-04-07T05:40:49.361Z INFO cmd/app.go:29 call server response = name:"Hello laurence" id:"v1.0.0"

4.2 创建基于用户身份的路由

执行以下命令以修改/创建路由,该路由将所有请求头存在 user: admin 标识的流量都路由至 v2 版本。

  1. $ kubectl apply -f virtualservice.yaml

virtualservice.yaml 内容

  1. apiVersion: networking.istio.io/v1alpha3
  2. kind: VirtualService
  3. metadata:
  4. name: dubbo-go-server
  5. spec:
  6. hosts:
  7. - dubbo-go-server
  8. http:
  9. - match:
  10. - headers:
  11. user:
  12. exact: admin
  13. route:
  14. - destination:
  15. host: dubbo-go-server
  16. subset: v2
  17. - route:
  18. - destination:
  19. host: dubbo-go-server
  20. subset: v1

4.3 验证路由生效

所有流量将流向 v2 版本应用。

  1. $ kubectl get pods | grep client | awk '{print $1}' | xargs kubectl logs
  2. ...
  3. 2022-04-07T05:52:18.714Z INFO cmd/app.go:35 call server response = name:"Hello laurence" id:"v2.0.0"
  4. 2022-04-07T05:52:19.716Z INFO cmd/app.go:35 call server response = name:"Hello laurence" id:"v2.0.0"
  5. 2022-04-07T05:52:20.717Z INFO cmd/app.go:35 call server response = name:"Hello laurence" id:"v2.0.0"
  6. 2022-04-07T05:52:21.718Z INFO cmd/app.go:35 call server response = name:"Hello laurence" id:"v2.0.0"
  7. 2022-04-07T05:52:22.720Z INFO cmd/app.go:35 call server response = name:"Hello laurence" id:"v2.0.0"
  8. 2022-04-07T05:52:23.722Z INFO cmd/app.go:35 call server response = name:"Hello laurence" id:"v2.0.0"
  9. 2022-04-07T05:52:24.723Z INFO cmd/app.go:35 call server response = name:"Hello laurence" id:"v2.0.0"

5. 基于权重的路由

5.1 创建基于权重的路由

延续上述任务,我们执行以下命令以修改/创建路由,该路由将流量的 10% 导入新版本应用,进行灰度测试。

  1. $ kubectl apply -f virtualservice.yaml

virtualservice.yaml 内容

  1. apiVersion: networking.istio.io/v1alpha3
  2. kind: VirtualService
  3. metadata:
  4. name: dubbo-go-server
  5. spec:
  6. hosts:
  7. - dubbo-go-server
  8. http:
  9. - route:
  10. - destination:
  11. host: dubbo-go-server
  12. subset: v1
  13. weight: 90
  14. - destination:
  15. host: dubbo-go-server
  16. subset: v2
  17. weight: 10

5.2 验证路由生效

少数流量将流向 v2 版本。

  1. $ kubectl get pods | grep client | awk '{print $1}' | xargs kubectl logs
  2. ...
  3. 2022-04-07T05:55:52.035Z INFO cmd/app.go:35 call server response = name:"Hello laurence" id:"v1.0.0"
  4. 2022-04-07T05:55:53.036Z INFO cmd/app.go:35 call server response = name:"Hello laurence" id:"v1.0.0"
  5. 2022-04-07T05:55:54.037Z INFO cmd/app.go:35 call server response = name:"Hello laurence" id:"v1.0.0"
  6. 2022-04-07T05:55:55.039Z INFO cmd/app.go:35 call server response = name:"Hello laurence" id:"v1.0.0"
  7. 2022-04-07T05:55:56.041Z INFO cmd/app.go:35 call server response = name:"Hello laurence" id:"v1.0.0"
  8. 2022-04-07T05:55:57.043Z INFO cmd/app.go:35 call server response = name:"Hello laurence" id:"v1.0.0"
  9. 2022-04-07T05:55:58.045Z INFO cmd/app.go:35 call server response = name:"Hello laurence" id:"v1.0.0"
  10. 2022-04-07T05:55:59.047Z INFO cmd/app.go:35 call server response = name:"Hello laurence" id:"v1.0.0"
  11. 2022-04-07T05:56:00.049Z INFO cmd/app.go:35 call server response = name:"Hello laurence" id:"v1.0.0"
  12. 2022-04-07T05:56:01.050Z INFO cmd/app.go:35 call server response = name:"Hello laurence" id:"v2.0.0"
  13. 2022-04-07T05:56:02.053Z INFO cmd/app.go:35 call server response = name:"Hello laurence" id:"v1.0.0"
  14. 2022-04-07T05:56:03.055Z INFO cmd/app.go:35 call server response = name:"Hello laurence" id:"v1.0.0"

最后修改 December 16, 2022: Fix check (#1736) (97972c1)