Project: APISIX (Orchestration & Management / API Gateway)
File: traffic-split.md

title: traffic-split keywords: Apache APISIX API Gateway Traffic Split Blue-green Deployment Canary Deployment description: This document contains information about the Apache APISIX traffic-split Plugin, you can use it to dynamically direct portions of traffic to various Upstream services. <!-- --> The `traffic-split` Plugin can be used to dynamically direct portions of traffic to various Upstream services. This is done by configuring `match`, which are custom rules for splitting traffic, and `weighted_upstreams` which is a set of Upstreams to direct traffic to. When a request is matched based on the `match` attribute configuration, it will be directed to the Upstreams based on their configured `weights`. You can also omit using the `match` attribute and direct all traffic based on `weighted_upstreams`. :::note The traffic ratio between Upstream services may be less accurate since round robin algorithm is used to direct traffic (especially when the state is reset). ::: | Name | Type | Required | Default | Valid values | Description | |--|-|-||--|--| | rules.match | array[object] | False | | | Rules to match for conditional traffic split. By default the list is empty and the traffic will be split unconditionally. | | rules.match.vars | array. | | rules.weighted_upstreams | array[object] | False | | | List of Upstream configurations. | | weightedupstreams.upstreamid | string/integer | False | | | ID of the configured Upstream object. | | weighted_upstreams.upstream | object | False | | | Configuration of the Upstream. | | upstream.type | enum | False | roundrobin | [roundrobin, chash] | Type of mechanism to use for traffic splitting. `roundobin` supports weighted load and `chash` does consistent hashing. | | upstream.hashon | enum | False | vars | | Only valid if the `type` is `chash`. Supported `vars` (Nginx variables), `header` (custom header), `cookie`, `consumer`, and `varscombinations`. For more details, refer . | | upstream.key | string | False | | | Only valid if the `type` is `chash`. Finds the corresponding node `id` according to `hash_on` and `key` values. For more details, refer . | | upstream.nodes | object | False | | | IP addresses (with optional ports) of the Upstream nodes represented as a hash table. In the hash table, the key is the IP address and the value is the weight of the node. Setting `weight` to `0` means that a request is never forwarded to that node. | | upstream.timeout | object | False | 15 | | Timeout in seconds for connecting, sending and receiving messages. | | upstream.passhost | enum | False | "pass" | ["pass", "node", "rewrite"] | Configures the host when the request is forwarded to the upstream. Can be one of `pass`, `node` or `rewrite`. `pass`- transparently passes the client's host to the Upstream. `node`- uses the host configured in the node of the Upstream. `rewrite`- Uses the value configured in `upstreamhost`. | | upstream.name | string | False | | | Identifier for the Upstream for specifying service name, usage scenarios etc. | | upstream.upstreamhost | string | False | | | Host of the Upstream request. Only valid when `passhost` attribute is set to

| | weighted_upstreams.weight | integer | False | weight = 1 | | Weight to give to each Upstream node for splitting traffic. | :::note Some of the configuration fields supported in Upstream are not supported in weightedupstreams.upstream. These fields are `servicename`, `discoverytype`, `checks`, `retries`, `retrytimeout`, `desc`, `scheme`, `labels`, `createtime`, and `updatetime`. As a workaround, you can create an Upstream object and configure it in `weightedupstreams.upstreamid` to achieve these functionalities. ::: :::info IMPORTANT In the `match` attribute configuration, the expression in variable is related as AND whereas multiple variables are related by OR. If only the `weight` attribute is configured, it corresponds to the weight of the Upstream service configured on the Route or Service. You can see this in action below. ::: You can configure the Plugin on a Route as shown below: :::note You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command: ```bash adminkey=$(yq '.deployment.admin.adminkey[0].key' conf/config.yaml | sed 's/"//g') ``` ::: ```shell curl http://127.0.0.1:9180/apisix/admin/routes/1 \ -H "X-API-KEY: $admin_key" -X PUT -d ' { "uri": "/index.html", "plugins": { "traffic-split": { "rules": [ { "weighted_upstreams": [ { "upstream": { "name": "upstream_A", "type": "roundrobin", "nodes": { "127.0.0.1:1981":10 }, "timeout": { "connect": 15, "send": 15, "read": 15 } }, "weight": 1 }, { "weight": 1 } ] } ] } }, "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:1980": 1 } } }' ``` Alternatively, you can configure `upstream_id` if you have already configured an Upstream object: ```shell curl http://127.0.0.1:9180/apisix/admin/routes/1 \ -H "X-API-KEY: $admin_key" -X PUT -d ' { "uri": "/index.html", "plugins": { "traffic-split": { "rules": [ { "weighted_upstreams": [ { "upstream_id": 1, "weight": 1 }, { "weight": 1 } ] } ] } }, "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:1980": 1 } } }' ``` :::tip Configure via `upstream_id` to reuse Upstream's health detection, retires, and other functions. ::: :::note You can use both `upstream` configuration and `upstream_id` configuration together. ::: The examples below shows different use cases for using the `traffic-split` Plugin. This is the process of gradually rolling out a release by splitting an increasing percentage of traffic to the new release until all traffic is directed to the new release. To set this up, you can configure the `weight` attribute of your `weighted_upstreams` as shown below: ```shell curl http://127.0.0.1:9180/apisix/admin/routes/1 \ -H "X-API-KEY: $admin_key" -X PUT -d ' { "uri": "/index.html", "plugins": { "traffic-split": { "rules": [ { "weighted_upstreams": [ { "upstream": { "name": "upstream_A", "type": "roundrobin", "nodes": { "127.0.0.1:1981":10 }, "timeout": { "connect": 15, "send": 15, "read": 15 } }, "weight": 3 }, { "weight": 2 } ] } ] } }, "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:1980": 1 } } }' ``` Here, the weights are in the ratio 3:2 which means that 60% of the traffic reaches the Upstream service running on `:1981` (Plugin's Upstream) and 40% reaches the service running on `:1980` which is the Route's Upstream service. Now to test this configuration, if you make 5 requests, 3 will hit one service and 2 will hit the other: ```shell curl http://127.0.0.1:9080/index.html -i ``` ```shell HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8

hello 1980 ``` ```shell curl http://127.0.0.1:9080/index.html -i ``` ```shell HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 ... world 1981 ``` In this setup, user traffic is shifted from the "green" (production) environment to the "blue" (staging) environment once the new changes have been tested and accepted within the blue environment. To set this up, you can configure `match` rules based on the request headers as shown below: ```shell curl http://127.0.0.1:9180/apisix/admin/routes/1 \ -H "X-API-KEY: $admin_key" -X PUT -d ' { "uri": "/index.html", "plugins": { "traffic-split": { "rules": [ { "match": [ { "vars": [ ["httprelease","==","newrelease"] ] } ], "weighted_upstreams": [ { "upstream": { "name": "upstream_A", "type": "roundrobin", "nodes": { "127.0.0.1:1981":10 } } } ] } ] } }, "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:1980": 1 } } }' ``` Here, if the request comes with a `release` header with value `new_release` it is directed to the new Upstream. Now if you send a request with `new_release` as the value for the `release` header, it will be directed to one Upstream and other requests will be directed to another Upstream. ```shell curl http://127.0.0.1:9080/index.html -H 'release: new_release' -i ``` ```shell HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 ... world 1981 ``` ```shell curl http://127.0.0.1:9080/index.html -H 'release: old_release' -i ``` ```shell HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 ... hello 1980 ``` You can also make custom releases by configuring rules and setting weights. In the example below, only one `vars` rule is configured and the multiple expressions in the rule have an AND relationship. The weights are configured in 3:2 ratio and traffic not matching the `vars` will be redirected to the Upstream configured on the Route. ```shell curl http://127.0.0.1:9180/apisix/admin/routes/1 \ -H "X-API-KEY: $admin_key" -X PUT -d ' { "uri": "/index.html", "plugins": { "traffic-split": { "rules": [ { "match": [ { "vars": [ ["arg_name","==","jack"], ["http_user-id",">","23"], ["http_apisix-key","~~","[a-z]+"] ] } ], "weighted_upstreams": [ { "upstream": { "name": "upstream_A", "type": "roundrobin", "nodes": { "127.0.0.1:1981":10 } }, "weight": 3 }, { "weight": 2 } ] } ] } }, "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:1980": 1 } } }' ``` After the rules are matched, 60% of the traffic hit the Upstream on port `1981` and 40% hit the one on `1980`. ```shell curl 'http://127.0.0.1:9080/index.html?name=jack' \ -H 'user-id:30' -H 'apisix-key: hello' -i ``` ```shell HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 ... world 1981 ``` If the rule fails to match, then the request is directed to the service on `1980`: ```shell curl 'http://127.0.0.1:9080/index.html?name=jack' \ -H 'user-id:30' -H 'apisix-key: hello' -i ``` ```shell HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 ... hello 1980 ``` In the example below, multiple `vars` rules are configured and they have an OR relationship. ```shell curl http://127.0.0.1:9180/apisix/admin/routes/1 \ -H "X-API-KEY: $admin_key" -X PUT -d ' { "uri": "/index.html", "plugins": { "traffic-split": { "rules": [ { "match": [ { "vars": [ ["arg_name","==","jack"], ["http_user-id",">","23"], ["http_apisix-key","~~","[a-z]+"] ] }, { "vars": [ ["arg_name2","==","rose"], ["http_user-id2","!",">","33"], ["http_apisix-key2","~~","[a-z]+"] ] } ], "weighted_upstreams": [ { "upstream": { "name": "upstream_A", "type": "roundrobin", "nodes": { "127.0.0.1:1981":10 } }, "weight": 3 }, { "weight": 2 } ] } ] } }, "upstream": { "type": "roundrobin", "nodes": {

1 } } }' ``` In the example below, both the `vars` rules are matched. After the rules are matched, 60% of the traffic is directed to the service on `1981` and 40% to the service on `1980`: ```shell curl 'http://127.0.0.1:9080/index.html?name=jack&name2=rose' \ -H 'user-id:30' -H 'user-id2:22' -H 'apisix-key: hello' -H 'apisix-key2: world' -i ``` ```shell HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 ... world 1981 ``` ```shell curl 'http://127.0.0.1:9080/index.html?name=jack&name2=rose' \ -H 'user-id:30' -H 'user-id2:22' -H 'apisix-key: hello' -H 'apisix-key2: world' -i ``` ```shell HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 ... hello 1980 ``` In the example below, the second `vars` rule fail to match. But since it is an OR relationship, the rules are matched and traffic is directed as configured: ```shell curl 'http://127.0.0.1:9080/index.html?name=jack' \ -H 'user-id:30' -H 'user-id2:22' -H 'apisix-key: hello' -H 'apisix-key2: world' -i ``` ```shell HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 ... world 1981 ``` ```shell curl 'http://127.0.0.1:9080/index.html?name=jack' \ -H 'user-id:30' -H 'user-id2:22' -H 'apisix-key: hello' -H 'apisix-key2: world' -i ``` ```shell HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 ... hello 1980 ``` In the example below the required headers are missing and both the `vars` rules fail to match and the request is directed to the default Upstream of the Route (`1980`): ```shell curl 'http://127.0.0.1:9080/index.html?name=jack' -i ``` ```shell HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 ... hello 1980 ``` You can achieve one-to-one correspondence between rules and Upstream by configuring multiple `rules`: For example, when the request header `x-api-id` is equal to `1` it should be directed to Upstream on port `1981` and if it is equal to `2` it should be directed to Upstream on port `1982`. And in other cases, it should default to the Upstream on port `1980`. You can configure this as shown below: ```shell curl http://127.0.0.1:9180/apisix/admin/routes/1 \ -H "X-API-KEY: $admin_key" -X PUT -d ' { "uri": "/hello", "plugins": { "traffic-split": { "rules": [ { "match": [ { "vars": [ ["http_x-api-id","==","1"] ] } ], "weighted_upstreams": [ { "upstream": { "name": "upstream-A", "type": "roundrobin", "nodes": { "127.0.0.1:1981":1 } }, "weight": 3 } ] }, { "match": [ { "vars": [ ["http_x-api-id","==","2"] ] } ], "weighted_upstreams": [ { "upstream": { "name": "upstream-B", "type": "roundrobin", "nodes": { "127.0.0.1:1982":1 } }, "weight": 3 } ] } ] } }, "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:1980": 1 } } }' ``` Now, when the request header `x-api-id` is equal to `1`, it will hit the Upstream on `1981`: ```shell curl http://127.0.0.1:9080/hello -H 'x-api-id: 1' ``` ```shell 1981 ``` If request header `x-api-id` is equal to `2`, it will hit the Upstream on `1982`: ```shell curl http://127.0.0.1:9080/hello -H 'x-api-id: 2' ``` ```shell 1982 ``` If request header `x-api-id` is equal to `3`, the rules do not match, and it will hit the Upstream on `1980`: ```shell curl http://127.0.0.1:9080/hello -H 'x-api-id: 3' ``` ```shell 1980 ``` To remove the `traffic-split` Plugin, you can delete the corresponding JSON configuration from the Plugin configuration. APISIX will automatically reload and you do not have to restart for this to take effect. ```shell curl http://127.0.0.1:9180/apisix/admin/routes/1 \ -H "X-API-KEY: $admin_key" -X PUT -d ' { "uri": "/index.html", "plugins": {}, "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:1980": 1 } } }' ```