### Background
I observed the Pull Request https://github.com/apache/apisix/pull/2279 adds labels for `upstream` object, which inspires me that if we attach labels for each node in `upstream`, then we can implement fancy traffic split feature. ### Design Traffic split, which means to setup several route rules and schedule requests by respecting these rules to specified upstream (or a subset nodes in a single upstream). With this feature, we can support the Canary Release (https://martinfowler.com/bliki/CanaryRelease.html#:~:text=Canary%20release%20is%20a%20technique,making%20it%20available%20to%20everybody.), Blue Green Release (https://docs.cloudfoundry.org/devguide/deploy-apps/blue-green.html#:~:text=Blue%2Dgreen%20deployment%20is%20a,live%20and%20Green%20is%20idle.) for backend applications when they are releasing, it's useful to reduce the downtime when something fault happens. But it's not a good idea to introduce these concepts into APISIX directly, since features like Blue Green Release is closer to business, not the infrastructure, so what we can do is introducing the concept traffic split , to abstract all the business logics into the traffic split rules (route rules), and it can be implemented as a plugin. Both the Istio and SMI (https://github.com/servicemeshinterface/smi-spec) support traffic split by `VirtualService` and `TrafficSplit` CRD respectively, although the latter is not perfect (at lease for now), we still can be aware of the similarity between them. In general, we need two parts to define a traffic split rule, match and route, the `match` defines the conditions that needed to judge wether a request is eligible to apply the `route`, for instance, a `match` might be "the method of HTTP request must be `GET`, and the parameter `id` must be equal to `1006`. From APISIX's point of view, match conditions can be scoped to HTTP, TLS, so conditions can vary, like URI, method, headers, SNI and even other APISIX instance-scoped variables (hostname, env and etc). The APISIX Route already defines a fantastic match mechanism which is similar with the traffic split. But that not means we don't need to embed the match part in traffic split plugin, instead, we can reuse the match mechanism in Route as it's own match mechanism to further split requests that hit the same Route. See https://github.com/api7/lua-resty-radixtree#new for more details about Route match. The route, decides the ultimate upstream that a request will go, either specifying the upstream name or with a label selector to filter an eligible subset from that upstream. What's more, multiple upstreams can be specified, each with a non-negative weight to support probabilistic selection. For now, node in APISIX Upstream doesn't contain labels attribute, so we can implement it by the metadata map in node indirectly. ```json # route requests to the nodes that has label release=canary in fake_upstream, { "route": [ { "upstream_id": "1", "labels": { "release": "canary" } } ] } # route 75% requests to upstream 2 and other 25% to upstream 3. { "route": [ { "upstream_id": "2", "weight": 75 }, { "upstream_id": "3", "weight": 25 } ] } ``` The Route already contains an upstream, after introducing the traffic split plugin, this upstream will be treated as the default upstream and will be used when requests can not hit the route rules. ### Examples #### Weighted Blue Green Release Say we have two upstreams `app-blue` (id: 1) and `app-green` (id: 2), which represent the old and new releases for `app` respectively. Now we want to route 10% requests which UA is Android to `app-green`. ```json [ { "match": [ { "vars": [ [ "http_user_agent", "~~", "Android"] ] } ], "route": [ { "upstream_id": 2, "weight": 10 } ] }, { "route": [ { "upstream_id": 1, "weight": 90 } ] } ] ``` #### Canary Release in a single upstream Say we updated a node in upstream `app` (id: 3), and this node have a unique label `release=canary`, other nodes in `app` has label `release=stable`, now we want to route requests which parameter `user_id` is between [13, 133] to this node. ```json [ { "match": [ { "vars": [ [ "arg_user_id", ">=", 13], [ "arg_user_id", "<=", 133] ] } ], "route": [ { "upstream_id": 3, "labels": "release=canary" } ] }, { "route": [ { "upstream_id": 3, "labels": "release=stable" } ] } ] ``` ### References * VirtualService: https://istio.io/latest/docs/reference/config/networking/virtual-service * TrafficSplit: https://github.com/servicemeshinterface/smi-spec/blob/master/apis/traffic-split/v1alpha3/traffic-split.md Chao Zhang [email protected]
