In a multicluster ambient mesh, services with the same name in the same namespace on
different clusters are treated as one logical service and their endpoints
are pooled. That behaviour, namespace sameness, is exactly what you want for high
availability: a catalog in two clusters becomes one catalog with
endpoints in both.
It is exactly what you do not want the moment two of those clusters are meant to be separate. Put a regulated workload and a general corporate workload in the same mesh and namespace sameness will happily merge a service that handles card data with one that does not. Segments are how you keep one mesh but draw a hard line through it.
The problem in one line
You want one mesh to operate (one control plane, one trust domain, one telemetry pipeline, one upgrade path) but several isolated worlds inside it, where the same namespace and service names can be reused safely and nothing crosses unless you say so. Namespace sameness alone gives you the single mesh but not the isolation. Segments add the isolation.
A real boundary: the cardholder data environment
PCI DSS requires you to segment the cardholder data environment (the CDE, everything that stores, processes or transmits card data) from the rest of the estate, so that out-of-scope systems cannot reach in. That is a boundary an auditor will ask you to prove. Here is that boundary as two segments in one mesh.
One mesh, two segments. card-vault is scope=segment, so it is only
published inside the CDE, the corporate side never receives its hostname and cannot route to it,
which is what an auditor wants to see. payment-api is scope=global,
the one sanctioned crossing point, and an AuthorizationPolicy on it allows only
the checkout identity from the corp cluster. Reuse of names, namespaces and the same
control plane on both sides, with a boundary you can prove.
What a segment actually is
A segment groups clusters, not workloads inside a cluster. It is two pieces that people often conflate:
- The
Segmentresource defines a segment: its name, its DNS domain suffix, and any hostname patterns. You create the same set of Segment resources on every peered cluster, so all clusters share one map of which segments exist. - A label on
istio-systemassigns a cluster to one of those segments. A cluster belongs to exactly one segment at a time.
So the number of Segment resources is the number of segments in the mesh (two, here), applied everywhere; the label is each cluster's membership card that picks one entry from the map. A corporate cluster still carries the CDE segment's definition, that is how it knows the CDE exists and what its domain is, it just is not a member of it.
# Both Segment resources exist on EVERY peered cluster (istio-system)
apiVersion: admin.solo.io/v1alpha1
kind: Segment
metadata:
name: corp
namespace: istio-system
spec:
domain: corp.bank
---
apiVersion: admin.solo.io/v1alpha1
kind: Segment
metadata:
name: cde
namespace: istio-system
spec:
domain: cde.bank
# Then each cluster joins ONE segment by labelling its istio-system namespace:
# kubectl --context corp-cluster label ns istio-system admin.solo.io/segment=corp
# kubectl --context cde-cluster label ns istio-system admin.solo.io/segment=cde
Name your own domains
The domain suffix is yours. Whatever you put in spec.domain becomes the suffix for
every service published in that segment, so a service resolves at
<service>.<namespace>.<domain>, for example
card-vault.payments.cde.bank. From 1.29 you can also add alias patterns
(RFC 6570 URI templates) for shorter or label-driven names:
apiVersion: admin.solo.io/v1alpha1
kind: Segment
metadata:
name: cde
namespace: istio-system
spec:
domain: cde.bank
aliases:
- pattern: "{service}.cde.bank" # card-vault.cde.bank
- pattern: "{service}.{labels['tier']}.cde.bank" # card-vault.data.cde.bank
ServiceEntry resources), only
for workloads inside the mesh. So you can pick cde.bank, tom.com, or
anything you like: you do not need to own it, it will not clash with the real public domain, and
it only resolves for callers in the mesh. Check what actually got published on a given cluster
with kubectl get serviceentry -n istio-system.
Deciding what crosses: visibility is not access
There are two separate controls, and mixing them up is the usual mistake.
| Control | What it decides | Set where | Enforced by |
|---|---|---|---|
solo.io/service-scope |
Visibility. segment = only publish inside my own segment.
global = publish to every segment. This decides whether a caller can even
resolve and route to the service. |
label on the Service | service discovery: a ServiceEntry is generated only where the service is
visible; elsewhere the name simply does not exist |
AuthorizationPolicy |
Access. Which specific identities may connect to what is visible. This is the security control. | policy targeting the Service (or its waypoint) | ztunnel at L4 (peer identity), or the waypoint at L7 (methods, paths, JWT claims) |
service-scope as a security feature. It controls what is reachable
by keeping a service out of other segments' service discovery, which is a strong boundary, but if
you need to enforce who may connect, that is AuthorizationPolicy.
So the CDE boundary is built from both. card-vault is scope=segment, so
it is never published outside the CDE, the corporate side has no route to it at all. The one
service that must cross, payment-api, is scope=global, and then an
AuthorizationPolicy pins down exactly who may call it:
# card-vault stays inside the CDE: never leaves the segment
kubectl label svc card-vault -n payments solo.io/service-scope=segment
# payment-api is the one sanctioned crossing point: visible mesh-wide
kubectl label svc payment-api -n payments solo.io/service-scope=global
...but visible is not open. Only checkout, from the corp cluster, may call it:
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: payment-api-allow-checkout
namespace: payments
spec:
targetRefs:
- kind: Service
group: ""
name: payment-api
action: ALLOW
rules:
- from:
- source:
principals:
- "corp.bank/ns/shop/sa/checkout" # exactly this identity
to:
- operation:
methods: ["POST"]
paths: ["/charge", "/tokenize"]
Targeting a specific cluster
Because each cluster can carry its own trust domain in the Solo distribution, the SPIFFE
principal on a request encodes which cluster the caller is in, not just its service
account. That is what lets you target access per cluster: an allow rule for
corp.bank/ns/shop/sa/checkout grants the checkout workload in the corporate
cluster and nothing else, a workload with the same service account in another cluster presents a
different principal and is denied. Put the policy on the waypoint if you need L7 conditions
(method, path, JWT claim); leave it at ztunnel for pure L4 identity allow or deny.
When segments are the wrong tool
Segments earn their place when you want isolation and a few shared services and one team operating the whole thing. If two estates share nothing, are run by different teams with different SLAs, and never need a service to cross, then separate meshes are simpler and you are not giving anything up. Reach for segments when the alternative, standing up and federating several independent meshes just to share a handful of services, costs more than drawing a line through one.
scope,
waypoints and AuthorizationPolicy actually land.