Skill for configuring the Apache APISIX traffic-split plugin via the a6 CLI. Covers weighted traffic splitting between upstreams with conditional match rules. Includes canary release, blue-green deployment, A/B testing patterns, and common operational workflows.
The traffic-split plugin dynamically directs portions of traffic to
different upstream services based on custom rules (match) and weighted
distributions (weighted_upstreams). Use it for canary releases, blue-green
deployments, and A/B testing — all without modifying DNS or load balancers.
| Field | Type | Required |
|---|
| Default |
|---|
| Description |
|---|
rules | array[object] | Yes | — | List of traffic splitting rules. Each rule has optional match and required weighted_upstreams. |
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
match | array[object] | No | [] | Conditions to activate this rule. Empty = unconditional (all traffic uses weights). |
match[].vars | array[array] | No | — | Variable expressions: ["variable", "operator", "value"]. Uses Nginx variables. Multiple vars in one object = AND. Multiple objects in match = OR. |
Operators: ==, ~=, >, <, >=, <=, ~~ (regex match), !~~, in, has, ! — see lua-resty-expr.
Common variables: arg_name (query param), http_header-name (request header), cookie_name (cookie value).
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
upstream_id | string/integer | No | — | ID of a pre-configured upstream object. Use this to get health checks, retries, etc. |
upstream | object | No | — | Inline upstream configuration (see below). |
weight | integer | No | 1 | Traffic weight for this upstream. |
If only weight is set (no upstream or upstream_id), traffic goes to the route's default upstream.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
type | string | No | "roundrobin" | Load balancing: "roundrobin" or "chash". |
nodes | object | Yes | — | Backend nodes as {"host:port": weight}. |
timeout | object | No | 15 (seconds) | {"connect": N, "send": N, "read": N} |
pass_host | string | No | "pass" | "pass" = client host, "node" = upstream node, "rewrite" = use upstream_host. |
upstream_host | string | No | — | Custom Host header. Only works with pass_host: "rewrite". |
name | string | No | — | Human-readable name for the upstream. |
Not supported in inline upstream: service_name, discovery_type, checks, retries, retry_timeout, scheme. Use upstream_id for these features.
a6 route create -f - <<'EOF'
{
"id": "canary-release",
"uri": "/api/*",
"plugins": {
"traffic-split": {
"rules": [
{
"weighted_upstreams": [
{
"upstream": {
"name": "new-version-v2",
"type": "roundrobin",
"nodes": {
"backend-v2:8080": 1
}
},
"weight": 2
},
{
"weight": 8
}
]
}
]
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"backend-v1:8080": 1
}
}
}
EOF
Result: 20% traffic → backend-v2, 80% → backend-v1 (route default).
a6 route create -f - <<'EOF'
{
"id": "blue-green",
"uri": "/api/*",
"plugins": {
"traffic-split": {
"rules": [
{
"match": [
{
"vars": [
["http_x-canary", "==", "true"]
]
}
],
"weighted_upstreams": [
{
"upstream": {
"name": "green-env",
"type": "roundrobin",
"nodes": {
"green-backend:8080": 1
}
},
"weight": 1
}
]
}
]
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"blue-backend:8080": 1
}
}
}
EOF
Result: Requests with header x-canary: true → green, all others → blue.
a6 route update canary-release -f - <<'EOF'
{
"plugins": {
"traffic-split": {
"rules": [
{
"weighted_upstreams": [
{
"upstream": {
"name": "new-version-v2",
"type": "roundrobin",
"nodes": {
"backend-v2:8080": 1
}
},
"weight": 5
},
{
"weight": 5
}
]
}
]
}
}
}
EOF
{
"plugins": {
"traffic-split": {
"rules": [
{
"match": [
{
"vars": [
["arg_variant", "==", "B"]
]
}
],
"weighted_upstreams": [
{
"upstream": {
"name": "variant-B",
"type": "roundrobin",
"nodes": {"variant-b:8080": 1}
}
}
]
}
]
}
}
}
Requests with ?variant=B → variant B backend.
{
"plugins": {
"traffic-split": {
"rules": [
{
"match": [{"vars": [["http_x-api-id", "==", "1"]]}],
"weighted_upstreams": [
{"upstream": {"type": "roundrobin", "nodes": {"svc-a:8080": 1}}}
]
},
{
"match": [{"vars": [["http_x-api-id", "==", "2"]]}],
"weighted_upstreams": [
{"upstream": {"type": "roundrobin", "nodes": {"svc-b:8080": 1}}}
]
}
]
}
}
}
{
"plugins": {
"traffic-split": {
"rules": [
{
"weighted_upstreams": [
{
"upstream_id": "canary-upstream",
"weight": 2
},
{
"weight": 8
}
]
}
]
}
}
}
Pre-create the upstream with a6 upstream create to configure health checks, retries, and other advanced settings.
{
"match": [
{
"vars": [
["arg_name", "==", "jack"],
["http_user-id", ">", "23"],
["http_x-env", "~~", "^(staging|canary)$"]
]
}
]
}
All three conditions must be true (AND logic within a single vars array).
| Structure | Logic |
|---|---|
Multiple entries in one vars array | AND — all must match |
Multiple objects in match array | OR — any can match |
Empty match or no match | Unconditional — always applies weights |
| Symptom | Cause | Fix |
|---|---|---|
| Traffic ratio inaccurate | Round-robin algorithm causes slight deviation | Expected behavior; ratios converge over many requests |
| Match rule not triggering | Variable name wrong or operator mismatch | Use http_header-name for headers, arg_name for query params |
| Health checks not working | Inline upstream doesn't support checks | Use upstream_id referencing a pre-created upstream with health checks |
| All traffic going to default | Match conditions never true | Debug with a6 route get and verify header/param names |
| Weight 0 not blocking traffic | Weight 0 means "never forward" to that upstream | Correct — set weight to 0 to exclude an upstream |