新手学分布式 – Envoy Proxy XDS Server动态设置的一点运用心得_腾讯云双十一,服务器

  • 新手学分布式 – Envoy Proxy XDS Server动态设置的一点运用心得_腾讯云双十一,服务器已关闭评论
  • 157 人浏览
  • A+
所属分类:教程分享 首页

Envoy Proxy 动态API的运用总结

Envoy Proxy和别的L4/L7反向理睬东西最大的区分就是原生支撑动态设置。 起首来看一下Envoy的大抵架构

新手学分布式 - Envoy Proxy XDS Server动态设置的一点运用心得_腾讯云双十一,服务器

从上图能够简朴明白:Listener担任吸收外部的要求,然后经由Filter/Router处置惩罚以后,在转发到细致的Cluster。 个中Listener,Router,Cluster和Host地点都是能够动态设置的,设置这些数据的效劳就称之为X Discovery Services,简称XDS。

本文重要形貌怎样编写XDS Server更新逻辑。

Envoy Porxy XDS Service经由过程GRPC效劳举行数据更新,一切Proto文件能够参考 https://github.com/envoyproxy/envoy/tree/master/api/envoy/api/v2 。 用户能够依据proto文件自行生成相对应言语的GRPC代码文件。假如运用golang来完成的话,Envoy已供应了一份编译好的GRPC代码,地点在这里: https://github.com/envoyproxy/go-control-plane/tree/master/envoy/api/v2

每一个XDS Service都有两种GRPC效劳, StreamDeltaStream用来更新全量数据,Delta用来更新增量数据。下面以RDS Service为例来看看怎样完成一个 XDS Service。

RDS Service能够供应一切的Route信息,一个简化后的典范Route设置以下:

# 完全的Route API定义参考 https://www.envoyproxy.io/docs/envoy/latest/api-v2/api/v2/rds.proto#envoy-api-msg-routeconfiguration

                route_config:
                  name: local_route
                  virtual_hosts:
                    - name: local_service
                      domains: ["*"]
                      require_tls: NONE
                      routes:
                        - match:
                            prefix: "/MyService"
                          route: { cluster: my-grpc-svc_cluster }

上面的设置语义为: 当收到一个Path前缀为/MyService的要求后,将此要求转发到my-grpc-svc_cluster. (my-grpc-svc_cluster示意的是后端Upstream信息,能够是STATIC范例也能够由CDS Service动态供应)

RDS Service的作用就是动态生成相似上面的语义设置。 先来看相对简朴的StreamRoutes怎样完成。

GRPC形貌文件中,对此函数的定义以下:

service RouteDiscoveryService {
  rpc StreamRoutes(stream DiscoveryRequest) returns (stream DiscoveryResponse) {
  }

  rpc DeltaRoutes(stream DeltaDiscoveryRequest) returns (stream DeltaDiscoveryResponse) {
  }

  rpc FetchRoutes(DiscoveryRequest) returns (DiscoveryResponse) {
    option (google.api.http) = {
      post: "/v2/discovery:routes"
      body: "*"
    };
  }
}

从返回值能够看出StreamRoutes是一个流函数,RDS会经由过程这个流及时将数据推送给Envoy。 所以大抵的完成模子就是以下的模样:

func (r rds) StreamRoutes(ls envoy_api_v2.RouteDiscoveryService_StreamRoutesServer) error {
    for{
        select{
            case x <- c>:
                ls.Send(xxx)
        }

    }
}

Send函数吸收的是DiscoveryResponse指针,而这个DiscoveryResponse从定义来看是自诠释动态构造体。 细致数据范例由typeUrl属性来决议。 细致到Route来讲,typeURL是"type.googleapis.com/envoy.api.v2.RouteConfiguration". (范例申明拜见 https://www.envoyproxy.io/docs/envoy/latest/api-docs/xds_protocol.html?highlight=type%20url#resource-types

数据则由Resource来保留。

Resource[]*any.Any范例,说白了就是全能的Interface{}。所以建立any.Any时须要指定细致的数据范例("type.googleapis.com/envoy.api.v2.RouteConfiguration"). data则是经由ProtoMessage编码后的二进制数据。 所以建立any.Any应当是下面的模样:


            data, err := proto.Marshal(xxxx)
            if err != nil {
                logrus.Errorf("Marshal Error. %s", err)
                continue
            }

            any :=  &any.Any{
                TypeUrl: "type.googleapis.com/envoy.api.v2.Cluster",
                Value:   data,
            })

xxxx是RDS须要返回给Envoy的路由数据,也就是RouteConfiguration。所以下面来看怎样构建RouteConfiguration。 经由过程API定义可知,有一些数据是必输项(经由过程proto校验形貌文件也能够猎取必输项,但不如看API文档来的直接)。 假定我们要完成开篇简朴的Route设置,那末 RouteConfiguration 应当如许定义:

      r:=&envoy_api_v2.RouteConfiguration{
            Name: "local_route",
            VirtualHosts: []*route.VirtualHost{
                &route.VirtualHost{
                    Name: "local_service",
                    Domains: []string{
                        "*",
                    },
                    Routes: []*route.Route{
                        &route.Route{
                            Match: &route.RouteMatch{
                                PathSpecifier: &route.RouteMatch_Prefix{Prefix: "/MyService"},
                            },
                            Action: &route.Route_Route{Route: &route.RouteAction{
                                ClusterSpecifier: &route.RouteAction_Cluster{Cluster: "my-grpc-svc_cluster"},
                            }},
                        },
                    },
                },
            },
        },

须要注重两个处所:

  1. Name: "local_route"。 这里的Name肯定要和Listener中定义的RouteConfig Name坚持一致。 假如不一致,Listener不会加载这段Route设置(换言之,这个Name就是两边的关联主键)
  2. Cluster 称号也要坚持一致。 同理,假如不一致,后续要求转发时就会找不到UPstream

经由这些步骤,一个近似完全的Route DiscoveryResponse就定义完成了。 然后就能够经由过程挪用Send来发送给Envoy。

但是此时事变并没有完毕, 开篇说过Stream同步全量,Delta同步增量。 再细致一点,在StreamRoutes中每次都须要传输当前一切的Route设置,而不仅仅是发生过更改的数据 . 个人感觉这类处置惩罚方式,关于数据构造来讲很贫苦,但关于Envoy数据更新来讲确很轻易(每次都是全量数据,不用做merge了)。 merge老是一件耗时辛苦的事变,就看事变谁来做,此次envoy决议让用户来做了。

所以我们须要调解一下StreamRoutes完成模子:

func (r rds) StreamRoutes(ls envoy_api_v2.RouteDiscoveryService_StreamRoutesServer) error {
    for{
        select{
            case x <- c>:
                // x示意更改的数据
                n := merge(x) //对x举行merge操纵,返回当前最新全量数据n

                var srvRoute []*route.Route
                for _, d := range n{
                    srvRoute = append(srvRoute, &route.Route{
                                        Match: &route.RouteMatch{
                                            PathSpecifier: &route.RouteMatch_Prefix{Prefix: xxxx},
                                        },
                                        Action: &route.Route_Route{Route: &route.RouteAction{
                                            ClusterSpecifier: &route.RouteAction_Cluster{Cluster: xxxx},
                                        }},
                                    })
                }

                rc := []*envoy_api_v2.RouteConfiguration{
                    &envoy_api_v2.RouteConfiguration{
                        Name: "local_route",
                        VirtualHosts: []*route.VirtualHost{
                            &route.VirtualHost{
                                Name: "local_service",
                                Domains: []string{
                                    "*",
                                },
                                Routes: srvRoute,
                            },
                        },
                    },
                }

                var resource []*any.Any

                for _, rca := range rc {
                    data, err := proto.Marshal(rca)
                    if err != nil {
                        return err
                    }

                    resource = append(resource, &any.Any{
                        TypeUrl: "type.googleapis.com/envoy.api.v2.RouteConfiguration",
                        Value:   data,
                    })
                }


                ls.Send(&envoy_api_v2.DiscoveryResponse{
                        VersionInfo: xxx,
                        Resources:   resource,
                        Canary:      false,
                        TypeUrl:     "type.googleapis.com/envoy.api.v2.RouteConfiguration",
                        Nonce:       time.Now().String(),
                    })
        }

    }
}

调解以后,每次就会返回Envoy最新的Route数据。 上面的模子仅斟酌了单Envoy实例的状况,并未斟酌多实例。 当多实例链接RDS Service时, 从c猎取数据,就会变成非幂等事宜,从而没法保证一切Envoy实例数据坚持一致。

完成StreamRoutes以后,在来看怎样完成DeltaRoutes

Delta是用来同步增量数据的,从函数原型来看,入参也是一个Stream,所以函数原型应当和StreamRoutes差不多。 假如你也如许想,就错了

Delta的stream只是用来传输数据的(猜想是为了进步数据传输效力,而并不是为了坚持长衔接)。 每次传输完成以后,Envoy都邑主动断开这个链接。 也就是说,Envoy是定时挪用DeltaRoutes来猎取增量更新数据的。假如根据stream的完成模子来编写逻辑,将会发明经由一段时间后,这个stream会莫名的变成closed状况。 缘由就是envoy吸收到此次事宜后,主动封闭了stream。

所以假如要运用Delta形式,那末会没法保证Envoy没法及时相应数据变化(由于这个定时挪用的存在)。 而假如运用Stream形式,那末用户须要自行保护数据正确性(假如merge很庞杂,正确性就会下落)。

所以挑选Stream照样Delta关于用户来讲是个问题。

腾讯云双十一活动