GCP ACE: Cloud Service Mesh Routing — VirtualService, DestinationRule, ServiceEntry, Gateway, and WorkloadEntry
The previous article covered what Cloud Service Mesh is and how a request flows through sidecars. This one covers the actual API objects you write to control that routing — Istio’s own CRDs, distinct from the plain Kubernetes Service/Ingress objects covered earlier in this series.
VirtualService — where a request actually goes
A VirtualService defines routing rules for traffic addressed to a host — which backend it actually reaches, based on rules you write (not just “whichever Pod matches the Service selector”).
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: reviews
namespace: default
spec:
hosts:
- reviews.default.svc.cluster.local
http:
- name: route
route:
- destination:
host: reviews
subset: v1
weight: 20
- destination:
host: reviews
subset: v2
weight: 80
This sends 20% of traffic to the v1 subset of reviews and 80% to v2 — a canary rollout, controlled entirely through config, with no changes to either version’s Deployment. Weights across all route entries for one HTTP rule must total 100.
DestinationRule — defining what a “subset” even means
A VirtualService’s subset: v1 only works because something elsewhere defines what v1 is. That’s the DestinationRule’s job — it groups a Service’s endpoints into subsets by label, and can also set policy (load balancing algorithm, connection pool limits, outlier detection) for the whole host or per subset.
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: reviews
namespace: default
spec:
host: reviews
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
The pairing to remember: VirtualService decides where traffic goes; DestinationRule defines what the destinations actually are and how to treat them. You virtually always write these two together — a VirtualService referencing a subset that no DestinationRule defines simply won’t resolve.
ServiceEntry — adding something outside the mesh to the mesh’s registry
Everything so far assumes the destination is a Service already inside the mesh. A ServiceEntry extends the mesh’s internal service registry to include something external — a third-party API, a legacy database outside Kubernetes — so mesh traffic rules, retries, and (optionally) mTLS can apply to it too, the same way they apply to internal services.
apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
name: external-payments-api
namespace: default
spec:
hosts:
- api.external-payments.example.com
location: MESH_EXTERNAL
ports:
- number: 443
name: https
protocol: TLS
resolution: DNS
location: MESH_EXTERNALtells Istio this service lives outside the mesh (as opposed toMESH_INTERNAL, for something inside the mesh registered this way for other reasons).resolution: DNSmeans Istio resolves the hostname via normal DNS, rather than expecting a static IP list.
Without a ServiceEntry, what happens to an unregistered external destination depends on the mesh’s outboundTrafficPolicy: with Istio’s default, ALLOW_ANY, the call is passed through untouched; with REGISTRY_ONLY, it’s blocked. Either way, explicitly registering the destination is what lets you apply mesh policy (timeouts, retries, TLS origination) to that external call.
Gateway (Istio) — not the same object as the Kubernetes Gateway API
This series already covered the Kubernetes Gateway API (Gateway/HTTPRoute, used for GKE’s single- and multi-cluster Gateway). Istio has its own, older Gateway resource — same name, different API group, different purpose, and the naming collision is easy to trip over in scenario questions.
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
name: ingress-gateway
namespace: istio-system
spec:
selector:
istio: ingressgateway # picks which gateway proxy deployment handles this
servers:
- port:
number: 443
name: https
protocol: HTTPS
tls:
mode: SIMPLE
credentialName: my-tls-secret
hosts:
- "shop.example.com"
An Istio Gateway only describes which ports/protocols/hosts are exposed at the mesh’s edge — it needs a VirtualService bound to it (via spec.gateways on the VirtualService) to actually define routing for the traffic that arrives there. A GKE Ingress or Kubernetes Gateway API resource, by contrast, is a complete routing object on its own.
WorkloadEntry and WorkloadGroup — bringing VMs into the mesh
Everything above assumes Kubernetes Pods. Real environments often still have a legacy VM that needs to participate in the mesh — call a meshed service with mTLS, or be called by one.
- WorkloadGroup — a template, created inside the cluster, describing what a group of non-Kubernetes workloads should look like to the mesh (labels, ports, service account) — conceptually similar to a Deployment’s Pod template, but for VMs.
- WorkloadEntry — a single VM instance’s actual registration, either created automatically when the VM’s Istio agent connects, or created manually.
apiVersion: networking.istio.io/v1beta1
kind: WorkloadGroup
metadata:
name: legacy-billing-vms
namespace: default
spec:
metadata:
labels:
app: legacy-billing
template:
serviceAccount: legacy-billing-sa
network: vm-network
apiVersion: networking.istio.io/v1beta1
kind: WorkloadEntry
metadata:
name: legacy-billing-vm-1
namespace: default
spec:
address: 10.10.0.5
labels:
app: legacy-billing
serviceAccount: legacy-billing-sa
Once registered this way, that VM gets a sidecar-equivalent agent, participates in mTLS like any Pod, and can be targeted by VirtualServices and DestinationRules exactly like a Kubernetes workload — useful for a gradual migration off VMs rather than an all-or-nothing cutover.
Cloud Service Mesh caveat: WorkloadGroup/WorkloadEntry is the open-source Istio mechanism. Google’s managed TRAFFIC_DIRECTOR control plane documents its own path for Compute Engine VMs instead: routing mesh traffic to VMs through a GCPBackend resource, with a migration guide from Istio ServiceEntry. Check the managed supported-features page before relying on an Istio VM feature in Cloud Service Mesh.
Network resilience: retries, timeouts, circuit breaking
A VirtualService can also define resilience behavior directly on a route, without touching application code:
http:
- route:
- destination:
host: reviews
subset: v1
retries:
attempts: 3
perTryTimeout: 2s
retryOn: 5xx,reset,connect-failure
timeout: 10s
And a DestinationRule’s trafficPolicy can add circuit breaking — ejecting an unhealthy instance from the load-balancing pool automatically:
spec:
host: reviews
trafficPolicy:
outlierDetection:
consecutive5xxErrors: 5
interval: 30s
baseEjectionTime: 60s
This ejects a backend after 5 consecutive 5xx responses, for at least 60 seconds — entirely at the infrastructure layer, with zero changes to the application.
Exam Takeaways
- VirtualService decides where traffic goes (routing rules, weights, retries, timeouts); DestinationRule defines what the destinations are (subsets by label) and how to treat them (load balancing, circuit breaking).
- A VirtualService referencing a subset needs a matching DestinationRule defining that subset — neither works alone for subset-based routing.
- ServiceEntry (
location: MESH_EXTERNAL,resolution: DNS) registers an external destination into the mesh so mesh policy applies to calls leaving the mesh, not just calls within it. - Istio’s own
Gatewayresource (networking.istio.io) is a different object from the Kubernetes Gateway API’sGateway(gateway.networking.k8s.io) used for GKE’s single/multi-cluster Gateway — same name, unrelated APIs. - An Istio Gateway only exposes ports/hosts at the mesh edge; it needs a VirtualService bound via
spec.gatewaysto actually route anything. - WorkloadGroup is the template for non-Kubernetes (VM) workloads; WorkloadEntry is an individual VM’s registration — together they let VMs join the mesh with mTLS like any Pod.
- Retries, per-try timeouts, and circuit breaking (outlier detection) are configured declaratively on VirtualServices/DestinationRules — no application code changes required.