Published on
· Updated

Istio Service Mesh for Spring Boot 4.1: Ambient Mode, Traffic, mTLS, and Authorization

Authors

A service mesh manages traffic between workloads. It can encrypt service-to-service connections, enforce workload identity, shift traffic between revisions, collect network telemetry, and centralize some retry or timeout policies.

It does not replace application architecture.

Istio cannot decide whether a payment retry is safe, make a database transaction atomic across services, validate business authorization, or repair an incompatible deployment. Those responsibilities remain in Spring Boot and the domain model.

This guide uses Spring Boot 4.1, Java 25, Kubernetes, and Istio 1.30.3. It uses Istio's ambient data plane as the baseline and adds a waypoint only where Layer 7 routing and policy are required.

TL;DR Enroll workloads in ambient mode for transparent Layer 4 mTLS through ztunnel. Add a waypoint for HTTP routing, Layer 7 authorization, and full request metrics. Use Kubernetes Gateway API resources for new ambient traffic configuration. Keep end-user authentication and business authorization in the application, and propagate trace context from Spring Boot clients.

What a Service Mesh Owns

A useful boundary is:

ConcernMeshSpring Boot application
Workload-to-workload mTLSYesUsually no certificate handling
Percentage traffic shiftingYesMust keep versions compatible
Network timeout policyYesMust define business deadline
Transport retrySometimesMust decide whether retry is safe
Workload identityYesUses service identity when needed
End-user JWT validationCan assistApplication still owns business access
Database transactionNoYes
Kafka processing guaranteeNoYes
Domain authorizationNoYes
Application spans and business attributesNoYes

The mesh is strongest when it removes repeated transport policy without pretending that transport policy is business correctness.

Sidecar, Ambient, and Proxyless Modes

Istio supports more than one data-plane model.

Sidecar mode

Each application Pod receives an Envoy sidecar.

Pod
  |- Spring Boot container
  `- Envoy sidecar

Advantages:

  • mature and feature-rich;
  • per-workload Layer 7 processing;
  • broad compatibility with Istio's traditional networking APIs.

Costs:

  • one proxy per Pod;
  • additional CPU and memory per workload;
  • Pod restart is normally required when adding or removing injection;
  • application and proxy lifecycle are coupled.

Ambient mode

Ambient mode separates the data plane into two layers.

Node
  `- ztunnel
       |- transparent Layer 4 mTLS
       `- workload identity

Namespace or service, when needed
  `- waypoint proxy
       |- HTTP routing
       |- Layer 7 authorization
       `- full request telemetry

A workload can join ambient mode without adding a second container to its Pod. ztunnel provides the secure Layer 4 overlay. A waypoint is optional and should be deployed only where Layer 7 behavior is required.

Proxyless mode

Some clients can integrate directly with Istio without a sidecar. This is specialized and is not the baseline for an ordinary Spring Boot HTTP service.

Choose a Mode Per Namespace

Sidecar and ambient workloads can coexist in one mesh, but a namespace should normally use one mode consistently.

Choose ambient when:

  • reducing per-Pod proxy overhead matters;
  • most workloads need mTLS but only some need Layer 7 policy;
  • the team wants Gateway API-based routing;
  • gradual enrollment without injecting sidecars is valuable.

Choose sidecar when:

  • a required feature is not supported by waypoints;
  • existing production policy depends heavily on VirtualService, DestinationRule, or EnvoyFilter;
  • migration risk is higher than the operational benefit;
  • the current platform and troubleshooting practices are sidecar-oriented.

Do not relabel a production namespace blindly. A sidecar-to-ambient migration needs an ordered plan, policy review, observability comparison, and rollback procedure.

Supported Versions Matter

At the time of this update, Istio 1.30 is a supported release line and 1.30.3 is the current patch. Istio 1.30 officially supports Kubernetes 1.32 through 1.36.

Do not copy an old Istio 1.21 installation command into a current cluster. Match:

Istio release
Kubernetes release
Gateway API CRD release
CNI and network policy behavior
observability integrations

Use the exact Istio documentation for the release installed in production.

Install Istio in Ambient Mode

Download the matching CLI:

curl -L https://istio.io/downloadIstio | sh -
cd istio-1.30.3
export PATH="$PWD/bin:$PATH"

istioctl version

Install the ambient profile:

istioctl install \
  --set profile=ambient \
  --skip-confirmation

Install the Kubernetes Gateway API CRDs when the cluster does not already provide them:

kubectl get crd \
  gateways.gateway.networking.k8s.io \
  >/dev/null 2>&1 || \
kubectl apply --server-side \
  -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.5.1/experimental-install.yaml

The experimental CRD bundle is used here because some mesh-oriented Gateway API features are not in the standard bundle. Review the exact CRDs and feature stages required by your platform before production installation.

Verify the control and node data plane:

kubectl get pods -n istio-system
istioctl version
istioctl analyze --all-namespaces

A demo profile is appropriate for evaluation, not a production sizing or security decision. Production installation should be managed through reviewed Helm values or an IstioOperator-equivalent installation workflow supported by the organization.

Enroll a Namespace

Create a namespace and enable ambient mode:

kubectl create namespace spring-boot-app

kubectl label namespace spring-boot-app \
  istio.io/dataplane-mode=ambient

Verify enrollment:

istioctl ztunnel-config workloads \
  -n istio-system | grep spring-boot-app

Ambient workloads remain 1/1 because no sidecar container is injected. Checking for 2/2 containers is a sidecar-mode diagnostic, not an ambient-mode diagnostic.

Add a Waypoint for Layer 7

HTTP routing and Layer 7 policy require a waypoint.

istioctl waypoint apply \
  -n spring-boot-app \
  --enroll-namespace

Verify it:

kubectl get gateway -n spring-boot-app
kubectl get pods -n spring-boot-app

--enroll-namespace labels the namespace so traffic to its services uses the waypoint.

In ambient mode:

  • ztunnel enforces Layer 4 security;
  • waypoint enforces Layer 7 routing and policy;
  • VirtualService support for ambient is Alpha;
  • mixing VirtualService and Gateway API configuration for the same ambient traffic is unsupported;
  • EnvoyFilter is not a supported waypoint extension mechanism.

For a new ambient deployment, use Gateway API resources unless a documented feature gap requires another design.

Spring Boot Service Configuration

A mesh should not require transport-specific code in the business layer.

Minimal dependencies:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>4.1.0</version>
    <relativePath />
</parent>

<properties>
    <java.version>25</java.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webmvc</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-opentelemetry</artifactId>
    </dependency>
</dependencies>

Application configuration:

spring:
  application:
    name: review-service

management:
  endpoints:
    web:
      exposure:
        include: health,info,prometheus

  endpoint:
    health:
      probes:
        enabled: true

  tracing:
    sampling:
      probability: ${TRACE_SAMPLING_PROBABILITY:0.10}

  opentelemetry:
    tracing:
      export:
        otlp:
          endpoint: ${OTEL_TRACES_ENDPOINT:http://otel-collector.observability.svc.cluster.local:4318/v1/traces}

Use Kubernetes readiness and liveness groups rather than a custom /health endpoint:

readinessProbe:
  httpGet:
    path: /actuator/health/readiness
    port: 8080
  initialDelaySeconds: 5
  periodSeconds: 5

livenessProbe:
  httpGet:
    path: /actuator/health/liveness
    port: 8080
  initialDelaySeconds: 15
  periodSeconds: 10

Liveness should not depend on every downstream service. A temporary PostgreSQL or Kafka failure should not automatically create a restart loop.

Use an Auto-Configured HTTP Client

Spring Boot must propagate trace context across service calls. Istio proxies pass trace headers, but the application must forward them when it creates a new outbound request.

@Configuration
public class ProductClientConfiguration {

    @Bean
    RestClient productClient(
            RestClient.Builder builder
    ) {
        return builder
                .baseUrl(
                        "http://product-service"
                )
                .build();
    }
}
@Component
public class ProductClient {

    private final RestClient client;

    public ProductClient(
            RestClient productClient
    ) {
        this.client = productClient;
    }

    public ProductResponse getProduct(
            UUID productId
    ) {
        return client.get()
                .uri(
                        "/products/{id}",
                        productId
                )
                .retrieve()
                .body(
                        ProductResponse.class
                );
    }
}

Do not create a new unmanaged client for every request. The auto-configured builder carries Spring Boot's observation and tracing customizations.

A mesh trace without application propagation often becomes several disconnected proxy spans rather than one request trace.

Deploy Two Product Revisions

Use one stable service name plus version-specific backend services.

apiVersion: v1
kind: ServiceAccount
metadata:
  name: product-service
  namespace: spring-boot-app
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: product-service-v1
  namespace: spring-boot-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: product-service
      version: v1
  template:
    metadata:
      labels:
        app: product-service
        version: v1
    spec:
      serviceAccountName: product-service
      containers:
      - name: application
        image: registry.example.com/product-service:1.0.0
        ports:
        - name: http
          containerPort: 8080
        readinessProbe:
          httpGet:
            path: /actuator/health/readiness
            port: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: product-service-v2
  namespace: spring-boot-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: product-service
      version: v2
  template:
    metadata:
      labels:
        app: product-service
        version: v2
    spec:
      serviceAccountName: product-service
      containers:
      - name: application
        image: registry.example.com/product-service:2.0.0
        ports:
        - name: http
          containerPort: 8080
        readinessProbe:
          httpGet:
            path: /actuator/health/readiness
            port: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: product-service
  namespace: spring-boot-app
spec:
  selector:
    app: product-service
  ports:
  - name: http
    port: 80
    targetPort: http
---
apiVersion: v1
kind: Service
metadata:
  name: product-service-v1
  namespace: spring-boot-app
spec:
  selector:
    app: product-service
    version: v1
  ports:
  - name: http
    port: 80
    targetPort: http
---
apiVersion: v1
kind: Service
metadata:
  name: product-service-v2
  namespace: spring-boot-app
spec:
  selector:
    app: product-service
    version: v2
  ports:
  - name: http
    port: 80
    targetPort: http

The stable product-service name is what callers use. The route selects the version-specific services.

Do not put database credentials directly in a Deployment manifest. Use Kubernetes Secrets or an external secret-management integration.

Canary Routing with Gateway API

Attach an HTTPRoute to the stable Kubernetes Service:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: product-service
  namespace: spring-boot-app
spec:
  parentRefs:
  - group: ""
    kind: Service
    name: product-service
    port: 80

  rules:
  - backendRefs:
    - name: product-service-v1
      port: 80
      weight: 90
    - name: product-service-v2
      port: 80
      weight: 10

This sends approximately 90% of eligible requests to v1 and 10% to v2.

Weights are not a promise that exactly one of every ten requests reaches v2. Small samples, persistent connections, retries, and client behavior can produce uneven short-term results.

A safe canary requires more than a route:

v1 and v2 can read the same database schema
v1 and v2 understand the same event contracts
v2 passes readiness checks
v2 has enough capacity for the assigned traffic
metrics distinguish deployment versions
rollback is one reviewed route update

The mesh shifts traffic. It does not make incompatible versions safe.

Header-Based Testing Before Percentage Rollout

A team can route internal test traffic to v2 before general exposure.

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: product-service
  namespace: spring-boot-app
spec:
  parentRefs:
  - group: ""
    kind: Service
    name: product-service
    port: 80

  rules:
  - matches:
    - headers:
      - name: x-canary
        value: "true"
    backendRefs:
    - name: product-service-v2
      port: 80

  - backendRefs:
    - name: product-service-v1
      port: 80
      weight: 90
    - name: product-service-v2
      port: 80
      weight: 10

Do not trust an externally supplied canary header for a security decision. Strip or set it at a trusted gateway when it is intended only for internal rollout control.

Timeouts and Retries Need Application Semantics

A mesh can enforce request timeouts and transport retries, but policy must follow the operation's semantics.

Safe retry candidates often include:

  • idempotent reads;
  • requests carrying a provider-supported idempotency key;
  • operations whose duplicate is rejected by a durable unique constraint.

Dangerous retry candidates include:

  • non-idempotent payments;
  • email or notification sends without deduplication;
  • state transitions that generate a new identifier on every attempt;
  • requests whose body cannot be replayed safely.

A retry multiplies load on a failing dependency. If application code, mesh, gateway, and client all retry independently, one user request can become many downstream requests.

Set one end-to-end deadline and budget retries within it.

client deadline: 3 seconds
maximum attempts: 2
per-attempt timeout: less than remaining deadline
backoff: bounded

Do not configure “three retries everywhere” as a fleet-wide default.

Circuit Breaking Is Not Business Recovery

Connection-pool limits and outlier detection can protect a service from unhealthy endpoints. They do not decide what the user should see or whether a workflow should compensate.

Use mesh-level protection for:

  • limiting concurrent connections;
  • ejecting repeatedly unhealthy endpoints;
  • preventing one dependency from consuming every client connection;
  • reducing cascading network failures.

Use application logic for:

  • fallback content;
  • partial response semantics;
  • payment reconciliation;
  • workflow compensation;
  • durable retry queues.

The mesh can reject a request quickly. It cannot invent a correct business result.

Enforce Strict mTLS

Ambient traffic uses HBONE and mTLS between mesh workloads, but the default peer-authentication mode is permissive. A workload can still accept plaintext traffic unless policy rejects it.

Require encrypted in-mesh traffic:

apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: default
  namespace: spring-boot-app
spec:
  mtls:
    mode: STRICT

With ambient mode, DISABLE is not a meaningful peer-authentication mode for HBONE traffic. Plan transitions carefully when workloads outside the mesh still need access.

Before enforcing STRICT:

  • confirm every intended caller is in the mesh or enters through an approved gateway;
  • verify probes and platform integrations;
  • inspect current plaintext traffic;
  • confirm Prometheus scraping configuration;
  • test rollback.

mTLS answers:

Which workload identity opened this connection?
Is the connection encrypted?

It does not answer:

May this user refund order 123?

That remains application authorization.

Use Workload Service Accounts

Each Deployment should have a dedicated Kubernetes ServiceAccount.

apiVersion: v1
kind: ServiceAccount
metadata:
  name: review-service
  namespace: spring-boot-app
spec:
  template:
    spec:
      serviceAccountName: review-service

Istio derives workload identity from the service account. Reusing the namespace's default service account across many applications weakens authorization policies because all those workloads share one identity.

Default-Deny with ALLOW Policies

The original form “action: DENY means deny everything, then list allowed rules” is incorrect. A DENY policy lists requests that must be denied. It does not contain exceptions that become allowed.

Istio evaluation is:

CUSTOM
then DENY
then ALLOW

When at least one ALLOW policy applies to a workload, requests that match none of its ALLOW rules are denied.

For ambient Layer 7 policy, attach the policy to the Kubernetes Service through the waypoint:

apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: product-service-allow-review
  namespace: spring-boot-app
spec:
  targetRefs:
  - group: ""
    kind: Service
    name: product-service

  action: ALLOW

  rules:
  - from:
    - source:
        serviceAccounts:
        - spring-boot-app/review-service
    to:
    - operation:
        methods:
        - GET
        paths:
        - /products/*

Because an ALLOW policy now targets product-service, nonmatching requests to that target are denied.

Use dry-run before enforcement where practical:

metadata:
  annotations:
    istio.io/dry-run: "true"

Review actual traffic before changing the annotation.

For waypoint-enforced policy, use targetRefs. A selector-based policy with HTTP conditions is not a substitute in ambient mode. ztunnel enforces Layer 4 policy and cannot evaluate HTTP methods or paths.

Layer 4 and Layer 7 Enforcement Are Separate

A strict design may use two controls:

ztunnel policy:
Only traffic from an approved gateway or waypoint identity may reach the workload.

waypoint policy:
Only review-service may call GET /products/*.

This prevents direct traffic from bypassing the waypoint while keeping HTTP authorization at the waypoint.

Apply such policies gradually. An incomplete Layer 4 allow list can block the waypoint itself.

JWT Validation Does Not Automatically Require JWT

Istio RequestAuthentication validates a token when a token is present. A request with no token can still pass unless an AuthorizationPolicy requires an authenticated request principal.

Example gateway JWT validation:

apiVersion: security.istio.io/v1
kind: RequestAuthentication
metadata:
  name: public-api-jwt
  namespace: spring-boot-app
spec:
  targetRefs:
  - group: gateway.networking.k8s.io
    kind: Gateway
    name: public-api
  jwtRules:
  - issuer: https://identity.example.com/
    jwksUri: https://identity.example.com/.well-known/jwks.json
    audiences:
    - product-api

Then require authentication:

apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: public-api-require-jwt
  namespace: spring-boot-app
spec:
  targetRefs:
  - group: gateway.networking.k8s.io
    kind: Gateway
    name: public-api
  action: ALLOW
  rules:
  - from:
    - source:
        requestPrincipals:
        - "*"

Gateway authentication can reject an invalid or missing token at the edge. Spring Security should still enforce application roles, tenant membership, object ownership, and domain rules.

Do not forward unverified identity headers from an external client. Strip them at the edge and derive trusted identity only after authentication.

Expose an API with Kubernetes Gateway API

Create an ingress gateway:

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: public-api
  namespace: spring-boot-app
spec:
  gatewayClassName: istio
  listeners:
  - name: http
    protocol: HTTP
    port: 80
    allowedRoutes:
      namespaces:
        from: Same

Route external requests:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: review-api
  namespace: spring-boot-app
spec:
  parentRefs:
  - name: public-api

  hostnames:
  - api.example.com

  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /reviews
    backendRefs:
    - name: review-service
      port: 80

For production, terminate TLS with a certificate reference and expose only the required hosts.

Do not use hosts: ["*"] and plaintext HTTP as a production example. Host scoping and TLS are part of the security boundary.

External Egress Is Not Automatically Safe

A service mesh can register and route external destinations, but an egress gateway does not automatically prevent direct internet access.

A complete controlled-egress design may require:

  • ServiceEntry for approved destinations;
  • routing through an egress gateway;
  • Kubernetes NetworkPolicy or cloud firewall rules preventing bypass;
  • TLS verification;
  • DNS policy;
  • destination-specific authentication;
  • audit logs;
  • bounded retries and timeouts.

Do not persist arbitrary external URLs and assume the egress gateway prevents SSRF. Application-level destination allowlists remain necessary.

Mesh Metrics and Application Metrics Are Different

Istio can provide transport metrics such as:

request rate
response code
request duration
source workload
destination workload
connection bytes
mTLS status

Spring Boot provides application metrics such as:

order outcomes
database connection-pool use
Kafka consumer lag
business validation failures
cache hit ratio
workflow backlog

In ambient mode, ztunnel supplies Layer 4 telemetry. A waypoint exports the full set of Layer 7 request metrics for traffic that traverses it.

Do not replace application instrumentation with proxy metrics. A proxy can report HTTP 200 while the response contains a business failure.

Trace Context Must Cross the Application

Envoy can create proxy spans, but the application must propagate the trace headers when it creates downstream requests.

Use:

  • auto-configured RestClient.Builder;
  • auto-configured WebClient.Builder;
  • auto-configured RestTemplateBuilder;
  • supported Kafka instrumentation;
  • W3C Trace Context or the propagation format selected by the platform.

Avoid:

RestClient.create(
    "http://product-service"
);

when the application depends on Spring Boot's tracing interceptors.

The OTLP destination should normally be an OpenTelemetry Collector, not a backend-specific legacy endpoint.

Sampling should be deliberate:

management:
  tracing:
    sampling:
      probability: 0.10

A production default of 100% tracing can be expensive. Tail sampling at the collector can retain errors and slow traces, but it requires buffer and capacity planning.

Avoid Duplicate Telemetry

Do not enable all of these without a deliberate plan:

OpenTelemetry Java agent
Spring Boot OpenTelemetry starter
manual OpenTelemetry SDK
custom Micrometer tracing bridge
custom proxy span generation settings

Duplicate instrumentation can produce repeated spans and confusing service graphs.

Choose one application instrumentation path and verify:

one server span
one client span
correct parent-child relation
matching service name and version
mesh spans linked to the same trace

Service Mesh Observability Is Not Installed Automatically

Installing Istio does not guarantee that Prometheus, Grafana, Kiali, Jaeger, or Tempo are production-ready or even installed.

The demo profile and sample add-ons are for evaluation.

Production observability needs:

  • a supported metrics backend;
  • trace collector and backend;
  • access-log policy;
  • retention and cost limits;
  • authentication for dashboards;
  • alerting;
  • capacity planning;
  • telemetry pipeline monitoring.

Kiali visualizes the mesh but does not replace Prometheus, tracing, or application logs.

Fault Injection Belongs in Controlled Environments

Istio can inject delays and aborts to test resilience.

Useful scenarios:

product-service adds 500 ms latency
10% of requests return HTTP 503
one revision becomes unavailable
one dependency exceeds its deadline

Fault injection should be:

  • limited to a test namespace or explicit request cohort;
  • time-bounded;
  • reviewed;
  • observable;
  • easy to remove;
  • excluded from ordinary production traffic unless part of an approved game day.

Do not combine fault injection with retries without calculating the resulting load. A delayed request retried several times can amplify resource use.

Test Policies Before Production

A mesh policy is executable infrastructure code.

Static validation

istioctl analyze --all-namespaces
kubectl apply --dry-run=server -f mesh-config/

Effective route inspection

For sidecar workloads:

istioctl proxy-status
istioctl proxy-config routes \
  deploy/review-service \
  -n spring-boot-app

For ambient:

istioctl ztunnel-config workloads \
  -n istio-system

kubectl get gateway \
  -n spring-boot-app

kubectl describe httproute \
  product-service \
  -n spring-boot-app

Security tests

Verify all four cases:

approved service account + approved path -> allowed
approved service account + forbidden path -> denied
unapproved service account + approved path -> denied
plaintext caller outside mesh under STRICT mTLS -> denied

Canary tests

Verify:

  • v2 receives only the intended cohort;
  • v1 remains reachable;
  • both revisions can use the current schema;
  • rollback returns traffic to v1;
  • error and latency metrics are split by version.

A valid YAML file is not evidence that the intended policy is effective.

Upgrade Without Replacing Every Policy at Once

Istio upgrades affect a shared communication layer.

A safe process includes:

  1. review release notes and supported Kubernetes versions;
  2. run analyzers against existing resources;
  3. install or upgrade a canary control-plane revision;
  4. move a small namespace or workload cohort;
  5. compare traffic, mTLS, authorization, metrics, and traces;
  6. expand gradually;
  7. remove the old control plane only after validation.

For sidecar mode, control-plane upgrades and data-plane proxy upgrades are related but separate. Existing proxies do not automatically become the new version merely because istiod changed.

For ambient mode, review ztunnel and waypoint rollout separately. A waypoint is a shared Layer 7 dependency for the enrolled services, so capacity and disruption budgets matter.

Capacity Planning

A service mesh adds infrastructure work.

Measure:

  • ztunnel CPU and memory per node;
  • waypoint CPU and memory per namespace or service;
  • gateway capacity;
  • added request latency;
  • connection counts;
  • telemetry volume;
  • control-plane CPU and memory;
  • configuration propagation time;
  • certificate rotation behavior.

Ambient mode removes the per-Pod sidecar, but it does not remove proxy cost. It changes where that cost is paid.

A namespace waypoint can become a shared bottleneck when many services route through it. Scale it, define disruption budgets, and monitor saturation.

NetworkPolicy and Ambient Mode

Ambient mode redirects traffic through Istio's node data plane. Existing Kubernetes NetworkPolicy and CNI behavior must be tested.

A policy that worked before enrollment can accidentally block:

  • ztunnel traffic;
  • waypoint traffic;
  • application probes;
  • IPv4 or IPv6 paths used by the ambient data plane.

Do not assume Istio authorization replaces Kubernetes NetworkPolicy. They protect different layers:

NetworkPolicy
  -> network reachability

Istio mTLS and AuthorizationPolicy
  -> workload identity and service policy

Use both when the threat model requires both.

Common Failure Modes

Namespace is labeled ambient, but traffic is unchanged

Check:

  • Istio CNI and ztunnel health;
  • istio.io/dataplane-mode=ambient;
  • Pod opt-out labels;
  • existing sidecar annotations;
  • ztunnel workload enrollment;
  • CNI exclusions;
  • network policy.

Sidecar mode takes precedence when a Pod still has sidecar injection metadata.

HTTPRoute exists, but the split does not happen

Check:

  • waypoint readiness;
  • namespace or Service istio.io/use-waypoint label;
  • parentRefs Service and port;
  • backend Service names;
  • Accepted and ResolvedRefs conditions;
  • request destination uses the stable Service name;
  • conflicting routes.

Requests fail after STRICT mTLS

Check:

  • the caller is enrolled in the mesh;
  • ingress and egress gateway interoperability;
  • non-mesh monitoring agents;
  • direct Pod-IP calls;
  • PeerAuthentication scope;
  • NetworkPolicy.

Do not disable mTLS globally as the first diagnostic step. Identify the plaintext caller.

AuthorizationPolicy blocks everything

Check:

  • whether an ALLOW policy now creates default deny for the target;
  • service-account identity;
  • namespace;
  • waypoint targetRefs;
  • path and method matching;
  • whether the request actually traverses the waypoint;
  • direct workload traffic bypass;
  • dry-run logs.

The original incorrect pattern of combining action: DENY with supposed allow exceptions can cause unexpected denial.

AuthorizationPolicy allows too much

Check:

  • whether no ALLOW policy targets the workload;
  • broad empty rules;
  • namespace-wide policy scope;
  • reused default service accounts;
  • JWT validation without a policy requiring a request principal;
  • external identity headers forwarded without verification.

Mesh metrics exist, but business failures are invisible

Proxy metrics see transport behavior. Add application observations and business-outcome metrics.

Traces are fragmented

Check:

  • the Spring client came from an auto-configured builder;
  • propagation format;
  • sampling;
  • OTLP exporter;
  • collector health;
  • asynchronous context propagation;
  • duplicate instrumentation.

Ingress reaches the Service but not its waypoint policy

Ingress traffic does not automatically traverse the destination waypoint in every ambient configuration. Review the istio.io/ingress-use-waypoint label and the intended policy enforcement point.

Pod readiness fails after ambient enrollment

Check CNI and NetworkPolicy compatibility, probe rewrite behavior, ztunnel health, and both IPv4 and IPv6 policy paths.

When Not to Add Istio

Do not add a service mesh solely because the system uses microservices.

It may not be justified when:

  • only a few services communicate;
  • Kubernetes NetworkPolicy and ordinary ingress controls meet the security need;
  • application tracing and client libraries already solve the main problem;
  • the team cannot operate another critical platform layer;
  • workloads are extremely latency-sensitive and measured overhead is unacceptable;
  • the environment is not Kubernetes-oriented;
  • no owner exists for upgrades, certificates, gateways, and incident response.

A smaller system with explicit application policies is better than an unowned mesh.

Review Checklist

Before production:

  • Is the data-plane mode selected deliberately?
  • Are Istio and Kubernetes versions compatible?
  • Are Gateway API CRDs managed and versioned?
  • Are namespaces enrolled intentionally?
  • Which services require a waypoint?
  • Do routes use one API model consistently?
  • Are canary versions schema- and event-compatible?
  • Are retries limited to safe operations?
  • Is strict mTLS tested with every caller?
  • Does each workload have a dedicated service account?
  • Are ALLOW and DENY semantics understood?
  • Are waypoint policies attached with targetRefs?
  • Is end-user authorization still enforced in Spring Security?
  • Does trace context propagate through application clients?
  • Are metrics, traces, and logs capacity-planned?
  • Are ingress and egress bypass paths controlled?
  • Are policies tested and dry-run before enforcement?
  • Is upgrade and rollback documented?
  • Are ztunnel, waypoint, and gateway saturation monitored?

Conclusion

Istio is most useful when its responsibility is narrow and explicit.

For Spring Boot services on a current Kubernetes platform:

  • use ambient mode for transparent Layer 4 identity and mTLS when it fits the feature set;
  • add waypoints only where Layer 7 routing, policy, or telemetry is needed;
  • use Gateway API for new ambient traffic configuration;
  • keep canary revisions compatible at database and event boundaries;
  • treat retries as business-sensitive rather than universally safe;
  • use dedicated service accounts and explicit ALLOW policies;
  • require strict mTLS only after every intended caller is enrolled;
  • keep user and domain authorization in Spring Security and application code;
  • propagate trace context through auto-configured Spring clients;
  • test policy behavior, not just YAML syntax;
  • operate ztunnel, waypoints, gateways, and telemetry as production dependencies.

A service mesh does not make a distributed system correct. It gives the platform a consistent place to enforce transport policy while applications remain responsible for business correctness.

Official References