# Istio traffic management: Gateway, VirtualService, DestinationRule and policies

> The Istio objects behind a canary release in a sidecar mesh: ingress Gateway, VirtualService routing, DestinationRule subsets, strict mTLS with PeerAuthentication, AuthorizationPolicy and an EnvoyFilter.

Interactive graph: https://k8wiz.com/graphs/istio-gateway-virtualservice-destinationrule

Istio doesn't replace Kubernetes Services. It adds objects that configure the Envoy proxies in front of them. A Gateway opens a port on the ingress gateway, a VirtualService decides where each request goes, and a DestinationRule defines the subsets it can go to and how to connect to them.

Security is separate from routing. PeerAuthentication decides whether a workload accepts plaintext, and AuthorizationPolicy decides which callers may reach it. This graph uses the classic sidecar mode with Istio's own `networking.istio.io` API. With Gateway API you would write an HTTPRoute instead of the Gateway and VirtualService.

## Resources

### Client Browser

outside the cluster

Calls `https://reviews.example.com`.

### Deployment istio-ingressgateway

comes with an add-on, apps/v1, namespace istio-system

Envoy Pods at the edge of the mesh, labeled `istio: ingressgateway`, behind a `LoadBalancer` Service. They do nothing until a Gateway selects them.

### Gateway public

you write it, networking.istio.io/v1, namespace istio-system

Opens port 443 on the Pods matching `istio: ingressgateway` and terminates TLS for `reviews.example.com`. It has no routes. A VirtualService that names this Gateway supplies them.

### Secret reviews-tls

you write it, v1, namespace istio-system

The certificate for `credentialName`. It must be in the same namespace as the ingress gateway Pods, not the Gateway's or the app's.

### EnvoyFilter edge-response-header

you write it, networking.istio.io/v1alpha3, namespace istio-system

Patches the generated Envoy config directly, here adding a Lua filter on the ingress gateway that sets a response header. It's a last resort: it depends on Envoy internals that can change between Istio versions, so prefer a first-class API whenever one exists.

### VirtualService reviews

you write it, networking.istio.io/v1, namespace shop

The routing rules. It applies to requests through the `public` Gateway and, through `mesh`, to calls from inside the mesh. A test user goes to v2, and everyone else is split 90/10, with a timeout and retries.

### DestinationRule reviews

you write it, networking.istio.io/v1, namespace shop

Defines the subsets `v1` and `v2` by Pod label, and how to connect to them: a connection pool limit and outlier detection that ejects a Pod after five 5xx errors in a row.

### Service reviews

you write it, v1, namespace shop

A plain Service selecting `app: reviews`, so it covers both versions. Istio uses it to find the endpoints, then the DestinationRule subsets narrow them down by the `version` label.

### Deployment reviews-v1

you write it, apps/v1, namespace shop

The stable version. Its Pods carry `app: reviews` and `version: v1`, and each one gets an Envoy sidecar injected because the namespace has `istio-injection: enabled`.

### Deployment reviews-v2

you write it, apps/v1, namespace shop

The canary, with `version: v2`. Scaling it doesn't change its share of traffic. The VirtualService weights decide that.

### Deployment frontend

you write it, apps/v1, namespace shop

Calls `http://reviews:9080` from inside the mesh, running as the ServiceAccount `frontend`. Its sidecar applies the same VirtualService rules and upgrades the call to mTLS on its own.

### PeerAuthentication default

you write it, security.istio.io/v1, namespace shop

Named `default` with no selector, so it covers every workload in `shop`. `STRICT` makes the sidecars refuse plaintext, so only mesh workloads with a valid certificate can connect.

### AuthorizationPolicy reviews-allow

you write it, security.istio.io/v1, namespace shop

Applies to `app: reviews`. Once any ALLOW policy selects a workload, everything not allowed is denied. This one allows GET requests from the `frontend` ServiceAccount and from the ingress gateway. The caller identity comes from the mTLS certificate.

## How the resources connect

- Client Browser → Deployment istio-ingressgateway: HTTPS :443 (traffic)
- Gateway public → Deployment istio-ingressgateway: selector istio=ingressgateway (selects)
- Gateway public → Secret reviews-tls: credentialName (references)
- EnvoyFilter edge-response-header → Deployment istio-ingressgateway: workloadSelector (selects)
- VirtualService reviews → Gateway public: gateways (references)
- VirtualService reviews → DestinationRule reviews: subset v1 / v2 (references)
- DestinationRule reviews → Service reviews: host (references)
- Service reviews → Deployment reviews-v1: app=reviews (selects)
- Service reviews → Deployment reviews-v2: app=reviews (selects)
- Deployment istio-ingressgateway → Deployment reviews-v1: 90% (traffic)
- Deployment istio-ingressgateway → Deployment reviews-v2: 10% + tester (traffic)
- Deployment frontend → Service reviews: GET reviews:9080 (mTLS) (traffic)
- PeerAuthentication default → Deployment reviews-v1: STRICT mTLS (selects)
- PeerAuthentication default → Deployment reviews-v2: STRICT mTLS (selects)
- AuthorizationPolicy reviews-allow → Service reviews: selector app=reviews (selects)
- AuthorizationPolicy reviews-allow → Deployment frontend: allows principal (references)

## How it works

1. **The Gateway opens a port on the edge proxies.** The Gateway selects the `istio-ingressgateway` Pods and opens 443 with TLS for `reviews.example.com`, using the `reviews-tls` Secret from `istio-system`. Requests arrive, but without a VirtualService the gateway answers 404.
2. **The VirtualService attaches routes.** The VirtualService lists `istio-system/public` in `gateways`, so its rules apply at the edge. Because it also lists `mesh`, the same rules apply to sidecars calling `reviews` from inside the cluster.
3. **The DestinationRule defines the subsets.** The VirtualService routes to the subsets `v1` and `v2`, which only exist in the DestinationRule. Each subset is a label filter over the Service's endpoints. A route to a subset that no DestinationRule defines fails with 503 errors.
4. **Requests split 90/10.** Requests with `x-user: tester` match the first rule and always go to v2. Everything else is split by weight. The split happens per request in Envoy, so it holds even with one v2 Pod and ten v1 Pods.
5. **Strict mTLS inside the namespace.** PeerAuthentication `default` makes every sidecar in `shop` refuse plaintext. Calls from `frontend` still work, because its sidecar sends mTLS by default to any workload that has a sidecar.
6. **AuthorizationPolicy checks the caller.** The reviews sidecars read the caller's identity from its mTLS certificate, for example `cluster.local/ns/shop/sa/frontend`, and allow only the listed principals to send GET requests. Anything else gets `403 RBAC: access denied`.
7. **The EnvoyFilter patches what's left.** There's no Istio API for running a Lua script on responses, so an EnvoyFilter inserts one on the ingress gateway, before the router filter. Check every EnvoyFilter when you upgrade Istio.

## Common mistakes

- A VirtualService with a `gateways` list applies only to those gateways. If you list a Gateway and forget `mesh`, calls from inside the cluster skip your rules entirely.
- The Secret named in `credentialName` must be in the ingress gateway's namespace. cert-manager has to issue the certificate there too.
- Turning on `STRICT` before every client has a sidecar cuts off the ones without one. Start in `PERMISSIVE`, check that all traffic shows as mTLS in Kiali or the metrics, then switch.
- An ALLOW policy that matches nothing still denies everything not allowed. A typo in a principal blocks real traffic, so test with `istioctl x authz check <pod>`.
- `istioctl analyze -n shop` finds the usual mistakes: a VirtualService pointing at a subset with no DestinationRule, a Gateway selector that matches no Pods, or two VirtualServices for the same host.

## The manifests you write

```yaml
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
  name: public
  namespace: istio-system
spec:
  selector:
    istio: ingressgateway
  servers:
    - port:
        number: 443
        name: https
        protocol: HTTPS
      hosts: ["reviews.example.com"]
      tls:
        mode: SIMPLE
        credentialName: reviews-tls
---
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: edge-response-header
  namespace: istio-system
spec:
  workloadSelector:
    labels:
      istio: ingressgateway
  configPatches:
    - applyTo: HTTP_FILTER
      match:
        context: GATEWAY
        listener:
          filterChain:
            filter:
              name: envoy.filters.network.http_connection_manager
              subFilter:
                name: envoy.filters.http.router
      patch:
        operation: INSERT_BEFORE
        value:
          name: envoy.filters.http.lua
          typed_config:
            "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
            defaultSourceCode:
              inlineCode: |
                function envoy_on_response(handle)
                  handle:headers():add("x-served-by", "edge")
                end
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: reviews
  namespace: shop
spec:
  hosts:
    - reviews.example.com
    - reviews.shop.svc.cluster.local
  gateways:
    - istio-system/public
    - mesh
  http:
    - match:
        - headers:
            x-user:
              exact: tester
      route:
        - destination:
            host: reviews.shop.svc.cluster.local
            subset: v2
    - route:
        - destination:
            host: reviews.shop.svc.cluster.local
            subset: v1
          weight: 90
        - destination:
            host: reviews.shop.svc.cluster.local
            subset: v2
          weight: 10
      timeout: 5s
      retries:
        attempts: 2
        perTryTimeout: 2s
        retryOn: 5xx,reset,connect-failure
---
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: reviews
  namespace: shop
spec:
  host: reviews.shop.svc.cluster.local
  trafficPolicy:
    connectionPool:
      http:
        http2MaxRequests: 1000
    outlierDetection:
      consecutive5xxErrors: 5
      interval: 10s
      baseEjectionTime: 30s
  subsets:
    - name: v1
      labels:
        version: v1
    - name: v2
      labels:
        version: v2
---
apiVersion: v1
kind: Service
metadata:
  name: reviews
  namespace: shop
spec:
  selector:
    app: reviews
  ports:
    - name: http
      port: 9080
---
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: default
  namespace: shop
spec:
  mtls:
    mode: STRICT
---
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: reviews-allow
  namespace: shop
spec:
  selector:
    matchLabels:
      app: reviews
  action: ALLOW
  rules:
    - from:
        - source:
            principals:
              - cluster.local/ns/shop/sa/frontend
              - cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account
      to:
        - operation:
            methods: ["GET"]
```
