An Istio VirtualService lists three entries under spec.http: one matching `uri: prefix: /api/v2`, one matching a request header, and one with no `match` block at all. In what order does the proxy evaluate them, and what happens if the entry with no match block is written first?
answer
- order of the list matters
- no scoring, no specificity
- the empty rule catches everything
- match list is OR, fields are AND
basics
~20 sIstio evaluates a VirtualService's http entries top to bottom and takes the first whose match conditions succeed. An entry with no match block matches every request, so writing it first makes every rule below it unreachable.
solid answer
~40 sThe `http` list in a VirtualService is **ordered**, and the proxy uses **first match wins** — it walks the entries in the order you wrote them and takes the first whose `match` conditions all succeed. Specificity plays no part: a `/api/v2` prefix rule written below a catch-all never fires. Within a single entry, the `match` field is itself a list, and the elements are OR'd together, while the conditions inside one element (`uri`, `headers`, `method`, `sourceLabels`, `queryParams`) are AND'd. An entry with no `match` at all is the catch-all and belongs last. If no entry matches, the sidecar answers 404 rather than falling through to the plain Service. I confirm what the proxy actually holds with `istioctl proxy-config route`, since the ordering the proxy has is the ordering that matters.
code
yaml · 25 linesapiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: reviews
namespace: default
spec:
hosts:
- reviews
http:
- match:
- uri:
prefix: /api/v2
headers:
x-user-tier:
exact: gold
- uri:
prefix: /v2
route:
- destination:
host: reviews
subset: v2
- route:
- destination:
host: reviews
subset: v1go deeper
Be able to say that the http list is evaluated top to bottom, first match wins, and that a route entry with no match block is the catch-all and must be written last.
Explain the two levels of match semantics — OR across match list elements, AND across the fields inside one element — and name the conditions available, including uri, headers, method and sourceLabels.
Show how you verify what the proxy actually holds rather than what git says, using istioctl proxy-config route, and connect a sudden wave of sidecar 404s to a match list that stopped covering some traffic.
Own the question of who may edit routing for a shared hostname: one VirtualService per host is simplest, delegation makes multi-team ownership explicit, and merged rules across resources leave ordering you cannot reason about.
## What the resource actually is An Istio `VirtualService` is the routing rule for one or more hostnames. Its `spec.hosts` says which destination names the rules apply to, and `spec.http` is an **ordered list** of HTTP route entries. Each entry may carry: - `match` — a list of conditions that select requests, - `route` — one or more weighted destinations (or `redirect` / `delegate` instead), - modifiers such as `rewrite`, `timeout`, `retries`, `fault`, `mirror`, `headers`, `corsPolicy`. The list is a routing table, not a set of independent policies. ## First match wins, in authored order The proxy walks `spec.http` from top to bottom and stops at the **first** entry whose match conditions succeed. There is no scoring, no longest-prefix preference, and no notion of a more specific rule beating a broader one. Ordering is entirely yours to get right, which is why the catch-all belongs at the bottom: ```yaml apiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: reviews spec: hosts: - reviews http: - match: - uri: prefix: /api/v2 route: - destination: host: reviews subset: v2 - route: # catch-all — must be last - destination: host: reviews subset: v1 ``` Swap those two entries and the `/api/v2` rule becomes dead configuration. Nothing errors, nothing warns loudly, and every request quietly goes to `v1`. ## OR across match elements, AND inside one `match` is itself a list, and the two levels behave differently: ```yaml - match: - uri: prefix: /api/v2 headers: x-user-tier: exact: gold # AND: prefix /api/v2 *and* the header - uri: prefix: /v2 # OR: this alternative also selects the entry ``` The first element requires both the URI prefix **and** the header. The second element is a separate alternative — if either element succeeds, the entry is selected. Getting this backwards is the second most common authoring mistake after ordering: people write two conditions as two list elements expecting AND, and get a much broader rule than they intended. ## What a match element can inspect The usable conditions include `uri` (with `exact`, `prefix` or `regex`), `headers` (a map of header name to the same three forms), `method`, `authority`, `scheme`, `port`, `queryParams`, `withoutHeaders`, `ignoreUriCase`, and the caller-side selectors `sourceLabels` and `sourceNamespace`. `sourceLabels` is what lets you route by *who is calling* rather than by what they asked for — useful for giving a test deployment a deterministic path to a new version without exposing that path to real users. ## When nothing matches If a VirtualService exists for the host but no entry matches, the request does not fall through to the ordinary Service routing — the proxy returns **404**. A sudden wave of 404s from the sidecar (not from the application) right after a routing change is almost always a match list that no longer covers some requests, typically because someone tightened a `uri` match or moved the catch-all. ## Verifying the order the proxy actually has The manifest in git is not authority; the config the proxy holds is. Two commands settle it: ```bash istioctl proxy-config route deploy/productpage -o json istioctl analyze -n default ``` The first prints the route table in the order the proxy will evaluate it, so a shadowed rule is visible as an entry sitting below a catch-all. The second flags a set of common conflicts and misreferences before you go hunting. ## More than one VirtualService for the same host If two VirtualServices name the same host, their rules are merged, and the relative order of rules coming from different resources is **not** something you should rely on. Keep one VirtualService per host wherever you can. When several teams genuinely need to own pieces of one hostname's routing, the `delegate` field lets a root VirtualService hand a matched path to another VirtualService, which gives you an explicit, ordered hand-off instead of an accidental merge.
- What does the proxy do with a request that matches none of the http entries in a VirtualService that exists for that host?It returns 404 from the sidecar itself — it does not fall back to plain Service routing. So a burst of 404s that the application never logs points at a match list that stopped covering some traffic, usually after a `uri` match was tightened or the catch-all was moved or removed.
- Two VirtualServices in different namespaces both list the same host. What happens?Their rules are merged, but the relative order of rules from different resources is not something you should depend on — you can end up with a shadowed rule and no obvious cause. Keep one VirtualService per host, and when several teams must share a hostname, use a root VirtualService with `delegate` so the hand-off is explicit and ordered.
- How would you route only your QA deployment's calls to a new version, without exposing a path that real users could hit?Match on the caller rather than the request: `sourceLabels` (and `sourceNamespace`) in the match element select traffic by the labels of the calling workload, so only pods carrying that label reach the new subset. A header match is easier to forge from outside; source labels are asserted by the mesh about the client workload.
The http list works like a firewall rule chain: the packet takes the first rule that matches, so an 'allow all' rule at the top makes everything below it decoration.
saying these in an interview costs you the question
- Thinks the most specific match wins, like a routing table
- Assumes rules are evaluated as an unordered policy set
- Writes two match conditions as separate list elements expecting AND
- Believes an unmatched request falls back to the plain Service
- Puts the catch-all route first because it feels like a default