From 74153b53e76d5fcf63e1bb35aedfbfc142e8dd71 Mon Sep 17 00:00:00 2001 From: Seth Malaki Date: Tue, 15 Sep 2026 11:26:51 +0100 Subject: [PATCH 01/20] docs: add Istio sidecar to Ambient Mode migration topic Phil's draft from DOCS-3037, attachment 39169, committed as attached. Open ENG REVIEW comments stay in the file for now. Co-authored-by: Phil DiCorpo --- .../compliance/istio/migrate-from-sidecar.mdx | 432 ++++++++++++++++++ 1 file changed, 432 insertions(+) create mode 100644 calico-enterprise/compliance/istio/migrate-from-sidecar.mdx diff --git a/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx new file mode 100644 index 0000000000..532496040e --- /dev/null +++ b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx @@ -0,0 +1,432 @@ +--- +description: Migrate an existing Istio sidecar mesh to the Calico-managed Istio Ambient Mode service mesh, one namespace at a time, without rewriting your network policies. +--- + +# Migrate from Istio sidecar mode to Istio Ambient Mode + +If you already run Istio in sidecar mode, you can move to the version of Istio that $[prodname] bundles and manages, which runs in ambient mode. +This page describes the supported migration path: what to check before you start, how the cutover is sequenced, how to verify each namespace, and how to roll back. + +:::note + +Istio Ambient Mode is a tech preview feature. +Tech preview features are subject to significant changes before they become GA. + +::: + +## Why migrate + +Sidecar meshes put an Envoy proxy next to every pod. That proxy consumes memory and CPU per workload, has to be restarted with the application to pick up new configuration, and it terminates connections in a way that hides the original destination port from the host network. + +Istio Ambient Mode replaces per-pod sidecars with a per-node zTunnel proxy for Layer 4 mTLS, and optional Waypoint proxies for Layer 7 features. +On $[prodname], that comes with three additional benefits: + +* **Your network policies keep working.** $[prodname] ships an [enhanced zTunnel](about-istio-ambient.mdx#the-enhanced-ztunnel-proxy) that preserves the original destination port, so existing $[prodname] and Kubernetes network policies continue to match traffic exactly as they did before the traffic was encrypted. In upstream Istio Ambient Mode, all mesh traffic appears as port 15008 and policies have to be rewritten. +* **The mesh lifecycle is managed for you.** The Tigera Operator installs and upgrades istiod, the Istio CNI plugin, and zTunnel, and CVEs are addressed on the regular $[prodname] patch release cadence. +* **Flow visibility is unchanged.** Because ports and workload identity are preserved, $[prodname] flow logs and the flow visualizations in the web console keep showing service-to-service traffic the way they did before you enabled mTLS. + +## What changes when you migrate + +| Sidecar mode | After migrating to Istio Ambient Mode | +| --- | --- | +| Envoy sidecar in every pod | zTunnel DaemonSet in `calico-system`, one proxy per node | +| Istio control plane in `istio-system`, self-managed | istiod in `calico-system`, installed and upgraded by the Tigera Operator | +| `VirtualService` | `HTTPRoute` (Gateway API), attached to a Waypoint | +| `DestinationRule` | No change. Traffic policies are applied by the Waypoint | +| `AuthorizationPolicy` with L4 rules only | No change. zTunnel enforces these directly | +| `AuthorizationPolicy` with L7 rules | Must target a Waypoint with `targetRefs` instead of selecting pods | +| `RequestAuthentication` | Must target a Waypoint with `targetRefs` | +| `EnvoyFilter` | Not supported on Waypoints | +| `Gateway` (ingress) | No change | +| `PeerAuthentication` with `mode: DISABLE` | Not supported. Remove before migrating | +| $[prodname] network policy | No change, as long as you do not deploy Waypoints. See [Network policy during and after migration](#network-policy-during-and-after-migration) | + +## Before you begin + +### Supported starting point + +This procedure assumes: + +* $[prodname] is installed and managed by the Tigera Operator. +* Istio is installed in sidecar mode and you manage it yourself, with Helm or `istioctl`. +* Your workloads are pods. Virtual machine workloads cannot join an ambient mesh. + +### Unsupported configurations + +Migration is blocked if your mesh uses any of the following. Resolve these before you start. + +* Virtual machine workloads in the mesh. +* SPIRE as the certificate provider. +* `PeerAuthentication` resources with `mode: DISABLE`. +* Primary-remote multicluster topologies. + +Istio Ambient Mode on $[prodname] also has its own limitations, which apply after migration: + +* [Application layer network policies](../../network-policy/application-layer-policies/alp.mdx) are not compatible with the Istio service mesh. +* [Workload-based web application firewalls](../../threat/web-application-firewall.mdx) are not compatible with Istio Ambient Mode. +* Clusters that are part of a [cluster mesh](../../multicluster/index.mdx) are not supported. +* Connect-time load balancing is not compatible with Istio Ambient Mode. +* Destination ports are preserved only when Istio is deployed without Waypoint. If you deploy Waypoint, traffic through the Waypoint shows port 15008 as its destination port. + + + +### Layer 7 policy causes a gap in enforcement + +:::warning + +If your mesh enforces Layer 7 `AuthorizationPolicy` rules, there is no zero-downtime migration path. + +While a namespace is partly migrated, traffic that originates from a workload that still has a sidecar is sent straight to the destination zTunnel and bypasses the Waypoint, so Layer 7 rules attached to that Waypoint are not enforced for it. +If you need continuous Layer 7 enforcement, migrate the affected namespaces inside a maintenance window. + +::: + +Layer 4 enforcement is not affected. zTunnel enforces L4 `AuthorizationPolicy` rules for every migrated workload, and $[prodname] network policy is enforced throughout. + +### Inventory your mesh + +1. Record the Istio version and the namespaces that have injection enabled: + + ```bash + istioctl version + kubectl get namespace -L istio-injection -L istio.io/rev + ``` + +1. Back up every Istio resource you have. You need this to roll back. + + ```bash + kubectl get virtualservice,destinationrule,authorizationpolicy,requestauthentication,peerauthentication,sidecar,envoyfilter,wasmplugin,gateway,serviceentry \ + --all-namespaces -o yaml > istio-config-backup.yaml + ``` + +1. Find the policies that need a Waypoint. Any `AuthorizationPolicy` that matches on `paths`, `methods`, `headers`, `hosts`, or uses JWT claims is a Layer 7 policy: + + ```bash + kubectl get authorizationpolicy --all-namespaces -o yaml | grep -E 'paths|methods|headers|hosts|when' + ``` + +1. Confirm that no `PeerAuthentication` resource disables mTLS: + + ```bash + kubectl get peerauthentication --all-namespaces -o yaml | grep -B5 'mode: DISABLE' + ``` + +1. Install the Gateway API CRDs if you plan to use Waypoints or `HTTPRoute`. Most clusters do not have them by default. + + ```bash + kubectl get crd gateways.gateway.networking.k8s.io &> /dev/null || \ + kubectl apply --server-side -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.6.0/experimental-install.yaml + ``` + + + +## Plan your cutover + +The Tigera Operator installs istiod, the Istio CNI plugin, and zTunnel into the `calico-system` namespace, and it manages the Istio webhooks and CRDs. +It cannot run alongside a self-managed Istio control plane, because both installations claim the same cluster-scoped resources and both install an Istio CNI plugin on every node. + +**You must remove your self-managed Istio control plane before you create the `Istio` custom resource.** + + + +That constraint shapes the two supported cutover patterns. + +### Pattern 1: single maintenance window (recommended) + +Remove the self-managed control plane, install the $[prodname]-managed one, and migrate every namespace in one window. +This is the recommended pattern for most clusters, because at no point do you have workloads that are trying to establish mTLS with two different root certificate authorities. + +Choose this pattern if your cluster has a small number of meshed namespaces, or if you enforce Layer 7 policy. + +### Pattern 2: phased, namespace by namespace + +Remove the self-managed control plane, install the $[prodname]-managed one, then migrate namespaces in batches over a longer period. + +Pods that still have sidecars keep forwarding traffic using their cached configuration, but be aware of two constraints: + +* **Certificates are not rotated.** After the self-managed istiod is gone, sidecars can no longer refresh their workload certificates. Istio's default certificate lifetime is 24 hours, so plan to finish the migration well inside that window. +* **mTLS between sidecar and migrated workloads fails.** Sidecars trust the root CA of your old control plane, and zTunnel presents a certificate from the new one. Traffic between a not-yet-migrated workload and a migrated workload fails TLS verification unless you temporarily disable mTLS for those service pairs with a `DestinationRule`. + +Choose this pattern only if you can group namespaces so that services that talk to each other migrate together. + +## Network policy during and after migration + +This is the part of the migration that does not change, and it is worth verifying rather than assuming. + +* **Without Waypoints**, the enhanced zTunnel preserves the original destination port. Your existing $[prodname] and Kubernetes network policies match the same traffic before and after migration. No rewrite, no exception for port 15008. +* **With Waypoints**, traffic that goes through a Waypoint reports port 15008 as its destination port. Policies that select those workloads by destination port need to be updated to allow 15008. +* **Do not label $[prodname] namespaces or pods into the mesh.** Labeling $[prodname] components can interrupt cluster networking. To secure $[prodname] components, see [Secure Calico component communications](../../operations/comms/index.mdx). + +Before you start, snapshot the policies that apply to a representative workload so you can compare after the cutover: + +```bash +kubectl get networkpolicies.projectcalico.org,globalnetworkpolicies.projectcalico.org \ + --all-namespaces -o yaml > calico-policy-snapshot.yaml +``` + +## Step 1: Remove the self-managed Istio control plane + +Remove the control plane components only. Leave the injected sidecars and your Istio configuration resources (`VirtualService`, `AuthorizationPolicy`, and so on) in place for now: existing pods continue to serve traffic with their cached configuration, and you still need those resources for rollback. + +1. Remove sidecar injection from the namespaces you are about to migrate, so that no new pod comes up with a sidecar: + + ```bash + kubectl label namespace istio-injection- + ``` + + If you use revision labels: + + ```bash + kubectl label namespace istio.io/rev- + ``` + +1. Uninstall the self-managed control plane, including the Istio CNI plugin: + + ```bash + istioctl uninstall --purge + ``` + + With Helm: + + ```bash + helm uninstall istio-cni -n istio-system + helm uninstall istiod -n istio-system + helm uninstall istio-base -n istio-system + ``` + + :::note + `istioctl uninstall --purge` also removes the Istio CRDs. The Tigera Operator installs the CRDs that the bundled Istio needs, but your backed-up `VirtualService`, `AuthorizationPolicy`, and other resources are deleted with them. Confirm that `istio-config-backup.yaml` is complete before you run this command. + ::: + +1. Confirm that no Istio control plane components or CNI plugin remain: + + ```bash + kubectl get pods -n istio-system + kubectl get daemonset --all-namespaces | grep istio + ``` + +## Step 2: Install the $[prodname]-managed Istio + +1. Apply the `Istio` custom resource: + + ```bash + cat < + ``` + +1. Verify that it is ready: + + ```bash + kubectl get gateway waypoint -n + ``` + + The `READY` column must show `True`. + +1. Update your $[prodname] network policies to allow traffic to port 15008 for the workloads behind the Waypoint. See [Network policy during and after migration](#network-policy-during-and-after-migration). + + + +## Step 4: Enable ambient mode, namespace by namespace + +Follow this order. Doing it out of order can leave traffic handled by neither a sidecar nor zTunnel. + +1. **Attach workloads to the Waypoint**, if you deployed one: + + ```bash + kubectl label namespace istio.io/use-waypoint=waypoint + ``` + + To scope a Waypoint to a single service instead: + + ```bash + kubectl label service -n istio.io/use-waypoint=waypoint + ``` + +1. **Add the namespace to the mesh**: + + ```bash + kubectl label namespace istio.io/dataplane-mode=ambient + ``` + + The Istio CNI plugin redirects new and restarted pods in this namespace through zTunnel. Pods that still have a sidecar continue to use it: a sidecar takes precedence over ambient mode. + +1. **Confirm that sidecar injection is off** for the namespace. You did this in Step 1, but verify, because a re-injected sidecar silently keeps the workload out of ambient mode: + + ```bash + kubectl get namespace -L istio-injection -L istio.io/rev + ``` + +1. **Restart the workloads** to drop the sidecars: + + ```bash + kubectl rollout restart deployment -n + kubectl rollout status deployment -n + ``` + +1. **Remove the Layer 7 policies that only a sidecar could enforce.** Do this immediately after the restart, before you validate. + + :::warning + + Once the sidecars are gone, zTunnel enforces the remaining `AuthorizationPolicy` resources, but it only understands Layer 4 attributes. It drops the Layer 7 conditions from a policy rather than ignoring the policy. An `ALLOW` policy whose rules were entirely Layer 7 becomes an empty allow, which denies all traffic to the selected workloads. A `DENY` policy behaves the same way, with the opposite effect. + + ::: + + Delete the selector-based policies that contain Layer 7 rules, which you have already replaced with Waypoint-targeted equivalents: + + ```bash + kubectl delete authorizationpolicy -n + ``` + + Delete the `VirtualService` and `DestinationRule` resources you replaced with `HTTPRoute`: + + ```bash + kubectl delete virtualservice -n + kubectl delete destinationrule -n + ``` + + Keep every `AuthorizationPolicy` that uses only Layer 4 rules. zTunnel enforces those correctly. + +1. **Validate the namespace** before you move to the next one. See [Verify the migration](#verify-the-migration). + +Repeat for each namespace. Namespaces you have not labeled are unaffected, subject to the certificate and mTLS constraints described in [Pattern 2](#pattern-2-phased-namespace-by-namespace). + +## Verify the migration + +1. Pods no longer have a sidecar container. The ready count drops by one, for example from `2/2` to `1/1`: + + ```bash + kubectl get pods -n + ``` + +1. The workloads are known to zTunnel: + + ```bash + istioctl ztunnel-config workloads -n calico-system | grep + ``` + + The protocol column shows `HBONE` for migrated workloads. + +1. Your $[prodname] policies still match the same traffic. Because the enhanced zTunnel preserves the original destination port, flows for a migrated workload should appear with their application ports, not 15008. Check the flow logs or the flow visualizations in the web console for the namespace, and compare against `calico-policy-snapshot.yaml`. + + + +1. Application traffic behaves as before. Test the Layer 7 behavior you rely on: header-based routing, path restrictions, JWT validation. + +## Roll back + +Every step is independently reversible. Roll back in the reverse order of the step you completed. + +| Last completed step | Rollback | +| --- | --- | +| Waypoint attached | `kubectl label namespace istio.io/use-waypoint-` | +| Ambient mode enabled | `kubectl label namespace istio.io/dataplane-mode-` | +| Sidecar injection removed | `kubectl label namespace istio-injection=enabled` | +| Workloads restarted | Re-add the injection label, then `kubectl rollout restart deployment -n ` | +| Old policies deleted | `kubectl apply -f istio-config-backup.yaml` | +| $[prodname]-managed Istio installed | `kubectl delete istio.operator.tigera.io default`, then reinstall your self-managed control plane | + +After a rollback that re-injects sidecars, confirm that pods show `2/2` containers and that traffic flows before you continue. + +:::warning + +Applying the full `istio-config-backup.yaml` restores your original resources, but it also overwrites any ambient resources you created during the migration that share a name with a backed-up resource. Delete the ambient resources first, or apply individual resources from the backup. + +::: + +## What changes in observability after migration + +* **Metrics.** In sidecar mode, metrics are reported with `reporter="source"` and `reporter="destination"`. In ambient mode they are reported with `reporter="source"` from zTunnel and `reporter="waypoint"`. Update dashboards and alerting rules that filter on the reporter label. +* **Metrics merging.** Sidecar mode can merge application metrics into the proxy's metrics endpoint. Ambient mode does not. Configure Prometheus to scrape zTunnel, Waypoint, and application pods as separate targets. +* **Tracing.** Sidecar mode produces two spans per hop. Ambient mode produces one span per Waypoint. Adjust trace-based SLOs. +* **`istioctl proxy-status`** does not list ztunnel workloads. Use `istioctl ztunnel-config workloads -n calico-system` instead. +* **$[prodname] flow logs** are unchanged, which is the point of the enhanced zTunnel: you keep the same service-level view you had before the mesh encrypted the traffic. + +## Troubleshooting + +Check whether the Istio pods are deployed: + +```bash +kubectl get pods -n calico-system | grep 'istio\|ztunnel' +``` + +Check whether the Istio CRDs are deployed: + +```bash +kubectl get crd | grep istio +``` + +Check which pods and namespaces are in the mesh: + +```bash +istioctl ztunnel-config workloads -n calico-system +``` + +Check for errors logged by the zTunnel component: + +```bash +ZTUNNEL_PODS=$(kubectl get pod -n calico-system \ + -l app.kubernetes.io/name=ztunnel \ + -o jsonpath='{.items[*].metadata.name}') + +for P in $ZTUNNEL_PODS; do + echo "--- Checking logs for pod: $P ---" + kubectl logs $P -n calico-system 2>/dev/null | grep -i error +done +``` + +**A workload was labeled but never joined the mesh.** The pod most likely still has a sidecar, which takes precedence. Confirm that injection is removed from the namespace and restart the workload. + +**Traffic fails between a migrated and a not-yet-migrated namespace.** The two workloads trust different certificate authorities. Finish migrating both namespaces, or temporarily disable mTLS for that service pair with a `DestinationRule`. + +**A migrated workload is denied all traffic.** Check for an `AuthorizationPolicy` whose rules were entirely Layer 7. zTunnel drops the Layer 7 conditions, leaving an empty rule. Delete the policy and re-create it against a Waypoint with `targetRefs`. + +## Additional resources + +* [Istio Ambient Mode](about-istio-ambient.mdx) +* [Deploy Istio Ambient Mode on your cluster](deploy-istio-ambient.mdx) +* [Overview of Istio ambient mode](https://istio.io/latest/docs/ambient/overview/) +* [Ambient and Kubernetes NetworkPolicy](https://istio.io/latest/docs/ambient/usage/networkpolicy/) +* [Configuration options](../../reference/installation/api) From e92a1f4ce44099e1b855d706b2f50d5c260ccae6 Mon Sep 17 00:00:00 2001 From: Seth Malaki Date: Tue, 15 Sep 2026 11:27:06 +0100 Subject: [PATCH 02/20] docs: add the migration topic to the Istio Ambient Mode sidebar --- sidebars-calico-enterprise.js | 1 + 1 file changed, 1 insertion(+) diff --git a/sidebars-calico-enterprise.js b/sidebars-calico-enterprise.js index de01d7ebe9..7825a47390 100644 --- a/sidebars-calico-enterprise.js +++ b/sidebars-calico-enterprise.js @@ -529,6 +529,7 @@ module.exports = { items: [ 'compliance/istio/about-istio-ambient', 'compliance/istio/deploy-istio-ambient', + 'compliance/istio/migrate-from-sidecar', ], }, 'compliance/encrypt-cluster-pod-traffic', From 33b47b05a96bc134c1a40a62cbc632fcc414df45 Mon Sep 17 00:00:00 2001 From: Seth Malaki Date: Tue, 15 Sep 2026 11:31:56 +0100 Subject: [PATCH 03/20] docs: use MDX comment syntax for the ENG REVIEW notes Docusaurus 3 compiles these pages as MDX v3, where an HTML comment is a syntax error. The five notes broke the MDX build, and one broken file fails the whole site compile. Content is unchanged. The notes stay invisible in the rendered page. --- .../compliance/istio/migrate-from-sidecar.mdx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx index 532496040e..9e9902c75a 100644 --- a/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx +++ b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx @@ -68,7 +68,7 @@ Istio Ambient Mode on $[prodname] also has its own limitations, which apply afte * Connect-time load balancing is not compatible with Istio Ambient Mode. * Destination ports are preserved only when Istio is deployed without Waypoint. If you deploy Waypoint, traffic through the Waypoint shows port 15008 as its destination port. - +{/* ENG REVIEW: confirm eBPF dataplane + ambient status for the shipping version. Fixes for the host-probe/SNAT issue landed in projectcalico/calico#9159 and #9192; confirm whether eBPF mode is a supported configuration for the bundled Istio, and whether Waypoint on eBPF is supported (see istio/istio#55481). If it is not, it belongs in this list. */} ### Layer 7 policy causes a gap in enforcement @@ -118,7 +118,7 @@ Layer 4 enforcement is not affected. zTunnel enforces L4 `AuthorizationPolicy` r kubectl apply --server-side -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.6.0/experimental-install.yaml ``` - +{/* ENG REVIEW: pin the Gateway API version we test against, and state whether the operator installs the Gateway API CRDs itself when Waypoint logging is enabled. */} ## Plan your cutover @@ -127,10 +127,10 @@ It cannot run alongside a self-managed Istio control plane, because both install **You must remove your self-managed Istio control plane before you create the `Istio` custom resource.** - + 3. What is the tested cert lifetime for sidecars that keep running after their control plane is removed? The 24-hour figure below is the Istio default, not a Tigera-tested number. */} That constraint shapes the two supported cutover patterns. @@ -267,7 +267,7 @@ Deploy a Waypoint before you enable ambient mode for the namespace, but do not a 1. Update your $[prodname] network policies to allow traffic to port 15008 for the workloads behind the Waypoint. See [Network policy during and after migration](#network-policy-during-and-after-migration). - +{/* ENG REVIEW: IstioSpec exposes waypointLogging, which injects an l7-collector sidecar into Waypoint pods to produce L7 logs. If that is a supported, documented capability in this release, it belongs here as a Calico-specific benefit of running Waypoints, with a link to the L7 logs topic. Today the L7 logs page says L7 log collection is not compatible with a service mesh, which reads as a contradiction for customers who deploy Waypoints. */} ## Step 4: Enable ambient mode, namespace by namespace @@ -351,7 +351,7 @@ Repeat for each namespace. Namespaces you have not labeled are unaffected, subje 1. Your $[prodname] policies still match the same traffic. Because the enhanced zTunnel preserves the original destination port, flows for a migrated workload should appear with their application ports, not 15008. Check the flow logs or the flow visualizations in the web console for the namespace, and compare against `calico-policy-snapshot.yaml`. - + {/* ENG REVIEW: replace with the exact console navigation path and, if available, a screenshot, plus a CLI equivalent for customers without console access. */} 1. Application traffic behaves as before. Test the Layer 7 behavior you rely on: header-based routing, path restrictions, JWT validation. From 9c2d3aa816c26d7e0bdf90999dd434c40b78a74a Mon Sep 17 00:00:00 2001 From: Seth Malaki Date: Wed, 23 Sep 2026 18:44:15 +0100 Subject: [PATCH 04/20] docs: answer the cutover review note and add the cacerts step --- .../compliance/istio/migrate-from-sidecar.mdx | 47 ++++++++++++++----- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx index 9e9902c75a..291d016a20 100644 --- a/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx +++ b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx @@ -122,22 +122,47 @@ Layer 4 enforcement is not affected. zTunnel enforces L4 `AuthorizationPolicy` r ## Plan your cutover -The Tigera Operator installs istiod, the Istio CNI plugin, and zTunnel into the `calico-system` namespace, and it manages the Istio webhooks and CRDs. -It cannot run alongside a self-managed Istio control plane, because both installations claim the same cluster-scoped resources and both install an Istio CNI plugin on every node. +The Tigera Operator installs istiod, the Istio CNI plugin, and zTunnel into the `calico-system` namespace. +It cannot run alongside a self-managed Istio control plane. +Both installations use a validating webhook named `istiod-default-validator`, both add an Istio CNI plugin to the same CNI configuration on every node, and both control planes manage the same built-in `GatewayClass` resources. **You must remove your self-managed Istio control plane before you create the `Istio` custom resource.** -{/* ENG REVIEW: this is the crux of the topic and needs to be confirmed or corrected before publication. - 1. Can the operator-managed Istio coexist with a self-managed istiod in istio-system for the duration of a phased migration (CRD ownership, validating/mutating webhooks, two istio-cni plugins on one node)? If yes, this whole section changes and a phased path becomes the recommendation. - 2. Can the operator-managed istiod use an existing root CA (a cacerts secret), so that workloads that still have sidecars can complete mTLS with workloads that zTunnel now handles? The IstioSpec API currently exposes istiod, istioCNI, ztunnel, waypointLogging, and dscpMark only. - 3. What is the tested cert lifetime for sidecars that keep running after their control plane is removed? The 24-hour figure below is the Istio default, not a Tigera-tested number. */} +The operator creates the Istio CRDs only if they do not exist yet. +It does not change CRDs that your self-managed installation created. -That constraint shapes the two supported cutover patterns. +### Keep your root certificate authority + +If you gave your self-managed Istio your own certificate authority, it is stored in the `cacerts` secret in `istio-system`. +The $[prodname]-managed istiod reads a `cacerts` secret from `calico-system` instead. +Copy yours across before Step 1: + +```bash +kubectl get secret cacerts -n istio-system -o json \ + | jq 'del(.metadata.resourceVersion,.metadata.uid,.metadata.creationTimestamp,.metadata.managedFields,.metadata.ownerReferences) | .metadata.namespace="calico-system"' \ + | kubectl apply -f - +kubectl get secret cacerts -n calico-system +``` + +With the same root, sidecars and zTunnel trust each other's certificates, so migrated and not-yet-migrated workloads keep talking over mTLS during the migration. + +If `istio-system` has no `cacerts` secret, your self-managed istiod generated its own root. +The $[prodname]-managed istiod generates a new one, and the two roots do not trust each other. + +:::warning + +Copy the secret before you create the `Istio` custom resource. +Changing the root under a running $[prodname]-managed istiod splits the mesh: existing pods keep working, but new pods get no certificate and zTunnel loses its connection to istiod. + +::: + +These constraints shape the two supported cutover patterns. ### Pattern 1: single maintenance window (recommended) Remove the self-managed control plane, install the $[prodname]-managed one, and migrate every namespace in one window. -This is the recommended pattern for most clusters, because at no point do you have workloads that are trying to establish mTLS with two different root certificate authorities. +This is the recommended pattern for most clusters, because sidecar and migrated workloads only have to talk to each other for the length of the window. +If you could not copy a `cacerts` secret, traffic between them fails TLS verification until the last namespace is migrated. Choose this pattern if your cluster has a small number of meshed namespaces, or if you enforce Layer 7 policy. @@ -148,9 +173,9 @@ Remove the self-managed control plane, install the $[prodname]-managed one, then Pods that still have sidecars keep forwarding traffic using their cached configuration, but be aware of two constraints: * **Certificates are not rotated.** After the self-managed istiod is gone, sidecars can no longer refresh their workload certificates. Istio's default certificate lifetime is 24 hours, so plan to finish the migration well inside that window. -* **mTLS between sidecar and migrated workloads fails.** Sidecars trust the root CA of your old control plane, and zTunnel presents a certificate from the new one. Traffic between a not-yet-migrated workload and a migrated workload fails TLS verification unless you temporarily disable mTLS for those service pairs with a `DestinationRule`. +* **mTLS between sidecar and migrated workloads fails without a shared root.** If you did not copy a `cacerts` secret, sidecars trust the root CA of your old control plane, and zTunnel presents a certificate from the new one. Traffic between a not-yet-migrated workload and a migrated workload fails TLS verification unless you temporarily disable mTLS for those service pairs with a `DestinationRule`. -Choose this pattern only if you can group namespaces so that services that talk to each other migrate together. +Choose this pattern only if you copied your `cacerts` secret, or if you can group namespaces so that services that talk to each other migrate together. ## Network policy during and after migration @@ -419,7 +444,7 @@ done **A workload was labeled but never joined the mesh.** The pod most likely still has a sidecar, which takes precedence. Confirm that injection is removed from the namespace and restart the workload. -**Traffic fails between a migrated and a not-yet-migrated namespace.** The two workloads trust different certificate authorities. Finish migrating both namespaces, or temporarily disable mTLS for that service pair with a `DestinationRule`. +**Traffic fails between a migrated and a not-yet-migrated namespace.** The two workloads trust different certificate authorities, because the `cacerts` secret was not copied to `calico-system`. Finish migrating both namespaces, or temporarily disable mTLS for that service pair with a `DestinationRule`. **A migrated workload is denied all traffic.** Check for an `AuthorizationPolicy` whose rules were entirely Layer 7. zTunnel drops the Layer 7 conditions, leaving an empty rule. Delete the policy and re-create it against a Waypoint with `targetRefs`. From 928b79c6bbc61a917d9ad8b72ac62bd2ddeda888 Mon Sep 17 00:00:00 2001 From: Seth Malaki Date: Wed, 23 Sep 2026 18:44:25 +0100 Subject: [PATCH 05/20] docs: drop the manual Gateway API CRD install --- .../compliance/istio/migrate-from-sidecar.mdx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx index 291d016a20..cab561aa63 100644 --- a/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx +++ b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx @@ -111,14 +111,13 @@ Layer 4 enforcement is not affected. zTunnel enforces L4 `AuthorizationPolicy` r kubectl get peerauthentication --all-namespaces -o yaml | grep -B5 'mode: DISABLE' ``` -1. Install the Gateway API CRDs if you plan to use Waypoints or `HTTPRoute`. Most clusters do not have them by default. +:::note - ```bash - kubectl get crd gateways.gateway.networking.k8s.io &> /dev/null || \ - kubectl apply --server-side -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.6.0/experimental-install.yaml - ``` +Waypoints and `HTTPRoute` need the Gateway API CRDs. +You do not need to install them yourself: the Tigera Operator creates them when you create the `Istio` custom resource. +If your cluster already has them, the operator leaves them in place. -{/* ENG REVIEW: pin the Gateway API version we test against, and state whether the operator installs the Gateway API CRDs itself when Waypoint logging is enabled. */} +::: ## Plan your cutover From e5e2f6641a8f33731919a9f7b2fa53ec484aa5e5 Mon Sep 17 00:00:00 2001 From: Seth Malaki Date: Wed, 23 Sep 2026 18:44:35 +0100 Subject: [PATCH 06/20] docs: resolve the eBPF review note --- calico-enterprise/compliance/istio/migrate-from-sidecar.mdx | 2 -- 1 file changed, 2 deletions(-) diff --git a/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx index cab561aa63..bab3d6cc88 100644 --- a/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx +++ b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx @@ -68,8 +68,6 @@ Istio Ambient Mode on $[prodname] also has its own limitations, which apply afte * Connect-time load balancing is not compatible with Istio Ambient Mode. * Destination ports are preserved only when Istio is deployed without Waypoint. If you deploy Waypoint, traffic through the Waypoint shows port 15008 as its destination port. -{/* ENG REVIEW: confirm eBPF dataplane + ambient status for the shipping version. Fixes for the host-probe/SNAT issue landed in projectcalico/calico#9159 and #9192; confirm whether eBPF mode is a supported configuration for the bundled Istio, and whether Waypoint on eBPF is supported (see istio/istio#55481). If it is not, it belongs in this list. */} - ### Layer 7 policy causes a gap in enforcement :::warning From 59fa118e73ebbb7d926d9683540f0dbab5fb6b15 Mon Sep 17 00:00:00 2001 From: Seth Malaki Date: Wed, 23 Sep 2026 18:44:50 +0100 Subject: [PATCH 07/20] docs: answer the Waypoint logging review note --- calico-enterprise/compliance/istio/migrate-from-sidecar.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx index bab3d6cc88..2d51e98c07 100644 --- a/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx +++ b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx @@ -289,7 +289,9 @@ Deploy a Waypoint before you enable ambient mode for the namespace, but do not a 1. Update your $[prodname] network policies to allow traffic to port 15008 for the workloads behind the Waypoint. See [Network policy during and after migration](#network-policy-during-and-after-migration). -{/* ENG REVIEW: IstioSpec exposes waypointLogging, which injects an l7-collector sidecar into Waypoint pods to produce L7 logs. If that is a supported, documented capability in this release, it belongs here as a Calico-specific benefit of running Waypoints, with a link to the L7 logs topic. Today the L7 logs page says L7 log collection is not compatible with a service mesh, which reads as a contradiction for customers who deploy Waypoints. */} +By default, $[prodname] adds an `l7-collector` container to each Waypoint pod, so traffic through a Waypoint shows up in $[prodname] L7 logs. +To turn this off, set `waypointLogging: Disabled` in the `Istio` custom resource. +See [Collect L7 logs using the Waypoint collector](../../observability/elastic/l7/enable-waypoint-collector.mdx). ## Step 4: Enable ambient mode, namespace by namespace From fe7daa02825e909b99d1c2c05334174f05918976 Mon Sep 17 00:00:00 2001 From: Seth Malaki Date: Wed, 23 Sep 2026 18:45:05 +0100 Subject: [PATCH 08/20] docs: add console and Kibana checks to the verify step --- .../compliance/istio/migrate-from-sidecar.mdx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx index 2d51e98c07..93f4cd79b4 100644 --- a/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx +++ b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx @@ -373,9 +373,13 @@ Repeat for each namespace. Namespaces you have not labeled are unaffected, subje The protocol column shows `HBONE` for migrated workloads. -1. Your $[prodname] policies still match the same traffic. Because the enhanced zTunnel preserves the original destination port, flows for a migrated workload should appear with their application ports, not 15008. Check the flow logs or the flow visualizations in the web console for the namespace, and compare against `calico-policy-snapshot.yaml`. +1. Your $[prodname] policies still match the same traffic. Because the enhanced zTunnel preserves the original destination port, flows for a migrated workload should appear with their application ports, not 15008. - {/* ENG REVIEW: replace with the exact console navigation path and, if available, a screenshot, plus a CLI equivalent for customers without console access. */} + In the web console, select **Service Graph**, **Default**, and double-click the namespace. The flow logs panel at the bottom filters to that namespace. See [Network visualization](../../observability/visualize-traffic.mdx). + + In Kibana, search the `tigera_secure_ee_flows*` index for `dest_namespace: ` and check the `dest_port` field. See [Kibana dashboards and logs](../../observability/kibana.mdx). + + Compare the policies in each flow's `policies` field against `calico-policy-snapshot.yaml`. 1. Application traffic behaves as before. Test the Layer 7 behavior you rely on: header-based routing, path restrictions, JWT validation. From 220365aa374edea3e28ce1ced2c06e10b86758da Mon Sep 17 00:00:00 2001 From: Seth Malaki Date: Wed, 23 Sep 2026 18:45:22 +0100 Subject: [PATCH 09/20] docs: scope the no-rewrite promise to meshes without Waypoints --- calico-enterprise/compliance/istio/migrate-from-sidecar.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx index 93f4cd79b4..8683c697bb 100644 --- a/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx +++ b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx @@ -1,5 +1,5 @@ --- -description: Migrate an existing Istio sidecar mesh to the Calico-managed Istio Ambient Mode service mesh, one namespace at a time, without rewriting your network policies. +description: Migrate an existing Istio sidecar mesh to the Calico-managed Istio Ambient Mode service mesh, one namespace at a time. Without Waypoints, your network policies need no rewrite. --- # Migrate from Istio sidecar mode to Istio Ambient Mode From 83ccbe3c9db6e7292ce4c989dfbc9eeb98a8f9e3 Mon Sep 17 00:00:00 2001 From: Seth Malaki Date: Wed, 23 Sep 2026 18:45:41 +0100 Subject: [PATCH 10/20] docs: back up every Istio kind before the uninstall --- .../compliance/istio/migrate-from-sidecar.mdx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx index 8683c697bb..7142599d50 100644 --- a/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx +++ b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx @@ -93,10 +93,22 @@ Layer 4 enforcement is not affected. zTunnel enforces L4 `AuthorizationPolicy` r 1. Back up every Istio resource you have. You need this to roll back. ```bash - kubectl get virtualservice,destinationrule,authorizationpolicy,requestauthentication,peerauthentication,sidecar,envoyfilter,wasmplugin,gateway,serviceentry \ + kubectl get virtualservices,destinationrules,authorizationpolicies,peerauthentications,requestauthentications,\ + telemetries,sidecars,serviceentries,envoyfilters,wasmplugins,proxyconfigs,workloadentries,workloadgroups,\ + gateways.networking.istio.io \ --all-namespaces -o yaml > istio-config-backup.yaml ``` + If your cluster has the Gateway API CRDs, back up those resources too: + + ```bash + kubectl get gateways.gateway.networking.k8s.io,httproutes \ + --all-namespaces -o yaml > gateway-api-backup.yaml + ``` + + Write `gateways.networking.istio.io` in full. On a cluster that has the Gateway API CRDs, a plain `gateway` returns Gateway API resources instead of Istio ones. + Check that the backup files are not empty. + 1. Find the policies that need a Waypoint. Any `AuthorizationPolicy` that matches on `paths`, `methods`, `headers`, `hosts`, or uses JWT claims is a Layer 7 policy: ```bash From c194b3e603fb9b5333e457962766b3cd0ab75cbe Mon Sep 17 00:00:00 2001 From: Seth Malaki Date: Wed, 23 Sep 2026 18:45:56 +0100 Subject: [PATCH 11/20] docs: stop the L7 policy search from matching L4 conditions --- calico-enterprise/compliance/istio/migrate-from-sidecar.mdx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx index 7142599d50..9389b424d4 100644 --- a/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx +++ b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx @@ -109,12 +109,14 @@ Layer 4 enforcement is not affected. zTunnel enforces L4 `AuthorizationPolicy` r Write `gateways.networking.istio.io` in full. On a cluster that has the Gateway API CRDs, a plain `gateway` returns Gateway API resources instead of Istio ones. Check that the backup files are not empty. -1. Find the policies that need a Waypoint. Any `AuthorizationPolicy` that matches on `paths`, `methods`, `headers`, `hosts`, or uses JWT claims is a Layer 7 policy: +1. Find the policies that need a Waypoint. Any `AuthorizationPolicy` that matches on `paths`, `methods`, `hosts`, `requestPrincipals`, or a `when` key that starts with `request.` (headers and JWT claims) is a Layer 7 policy: ```bash - kubectl get authorizationpolicy --all-namespaces -o yaml | grep -E 'paths|methods|headers|hosts|when' + kubectl get authorizationpolicy --all-namespaces -o yaml | grep -i -E 'paths|methods|hosts|requestPrincipals|key: request\.' ``` + A `when` condition on `source.ip`, `source.namespace`, `source.principal`, `destination.ip`, or `destination.port` is Layer 4. zTunnel enforces those without a Waypoint. + 1. Confirm that no `PeerAuthentication` resource disables mTLS: ```bash From 1cf1c9ab01090ab9b26be1fec8e325c9a3b7c392 Mon Sep 17 00:00:00 2001 From: Seth Malaki Date: Wed, 23 Sep 2026 18:46:07 +0100 Subject: [PATCH 12/20] docs: match the Waypoint note to the Step 4 order --- calico-enterprise/compliance/istio/migrate-from-sidecar.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx index 9389b424d4..27ae53a9f2 100644 --- a/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx +++ b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx @@ -285,7 +285,7 @@ Remove the control plane components only. Leave the injected sidecars and your I Skip this step if all of your `AuthorizationPolicy` resources are Layer 4 only and you do not use `VirtualService` for routing, retries, fault injection, or header manipulation. -Deploy a Waypoint before you enable ambient mode for the namespace, but do not attach workloads to it until the sidecars are gone. A workload that has both a sidecar and a Waypoint processes traffic twice. +Deploy the Waypoint before you enable ambient mode for the namespace. Step 4 attaches workloads to it before the restart. A pod that still has a sidecar keeps using the sidecar, so traffic is not processed twice. 1. Deploy a Waypoint in the namespace: From 39510ba602e070acbaf93126b43a5acdbbe24932 Mon Sep 17 00:00:00 2001 From: Seth Malaki Date: Wed, 23 Sep 2026 18:46:21 +0100 Subject: [PATCH 13/20] docs: describe L7 policy on zTunnel the way Istio does --- calico-enterprise/compliance/istio/migrate-from-sidecar.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx index 27ae53a9f2..ecac8f37ad 100644 --- a/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx +++ b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx @@ -348,7 +348,7 @@ Follow this order. Doing it out of order can leave traffic handled by neither a :::warning - Once the sidecars are gone, zTunnel enforces the remaining `AuthorizationPolicy` resources, but it only understands Layer 4 attributes. It drops the Layer 7 conditions from a policy rather than ignoring the policy. An `ALLOW` policy whose rules were entirely Layer 7 becomes an empty allow, which denies all traffic to the selected workloads. A `DENY` policy behaves the same way, with the opposite effect. + Once the sidecars are gone, zTunnel enforces the remaining `AuthorizationPolicy` resources, but it only understands Layer 4 attributes. A policy with Layer 7 rules does not get ignored. It fails safe by becoming a `DENY` policy. An `ALLOW` policy with Layer 7 rules denies the traffic it used to allow, and a `DENY` policy with Layer 7 rules can deny more traffic than you intended. ::: @@ -463,7 +463,7 @@ done **Traffic fails between a migrated and a not-yet-migrated namespace.** The two workloads trust different certificate authorities, because the `cacerts` secret was not copied to `calico-system`. Finish migrating both namespaces, or temporarily disable mTLS for that service pair with a `DestinationRule`. -**A migrated workload is denied all traffic.** Check for an `AuthorizationPolicy` whose rules were entirely Layer 7. zTunnel drops the Layer 7 conditions, leaving an empty rule. Delete the policy and re-create it against a Waypoint with `targetRefs`. +**A migrated workload is denied all traffic.** Check for an `AuthorizationPolicy` with Layer 7 rules that still selects the workload. zTunnel treats it as a `DENY` policy. Delete the policy and re-create it against a Waypoint with `targetRefs`. ## Additional resources From b0cf7ee7aba52a5fe191685fe98d88e9010bdf52 Mon Sep 17 00:00:00 2001 From: Seth Malaki Date: Wed, 23 Sep 2026 18:46:33 +0100 Subject: [PATCH 14/20] docs: keep DestinationRules unless only their subsets moved --- .../compliance/istio/migrate-from-sidecar.mdx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx index ecac8f37ad..4365a77e5f 100644 --- a/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx +++ b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx @@ -32,7 +32,8 @@ On $[prodname], that comes with three additional benefits: | Envoy sidecar in every pod | zTunnel DaemonSet in `calico-system`, one proxy per node | | Istio control plane in `istio-system`, self-managed | istiod in `calico-system`, installed and upgraded by the Tigera Operator | | `VirtualService` | `HTTPRoute` (Gateway API), attached to a Waypoint | -| `DestinationRule` | No change. Traffic policies are applied by the Waypoint | +| `DestinationRule` traffic policy (connection pool, outlier detection, TLS) | No change. The Waypoint applies it | +| `DestinationRule` subsets used for routing | One Kubernetes `Service` per version, used as a `backendRef` in the `HTTPRoute` | | `AuthorizationPolicy` with L4 rules only | No change. zTunnel enforces these directly | | `AuthorizationPolicy` with L7 rules | Must target a Waypoint with `targetRefs` instead of selecting pods | | `RequestAuthentication` | Must target a Waypoint with `targetRefs` | @@ -358,10 +359,15 @@ Follow this order. Doing it out of order can leave traffic handled by neither a kubectl delete authorizationpolicy -n ``` - Delete the `VirtualService` and `DestinationRule` resources you replaced with `HTTPRoute`: + Delete the `VirtualService` resources you replaced with `HTTPRoute`: ```bash kubectl delete virtualservice -n + ``` + + Keep your `DestinationRule` resources. The Waypoint still applies their traffic policies. Delete one only if it defines nothing but subsets that you replaced with per-version services: + + ```bash kubectl delete destinationrule -n ``` From 9b2600d827bfa4b1376741be349406da27a2eb3e Mon Sep 17 00:00:00 2001 From: Seth Malaki Date: Wed, 23 Sep 2026 18:46:40 +0100 Subject: [PATCH 15/20] docs: restore the original injection label on rollback --- calico-enterprise/compliance/istio/migrate-from-sidecar.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx index 4365a77e5f..ffa7d260fe 100644 --- a/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx +++ b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx @@ -411,7 +411,7 @@ Every step is independently reversible. Roll back in the reverse order of the st | --- | --- | | Waypoint attached | `kubectl label namespace istio.io/use-waypoint-` | | Ambient mode enabled | `kubectl label namespace istio.io/dataplane-mode-` | -| Sidecar injection removed | `kubectl label namespace istio-injection=enabled` | +| Sidecar injection removed | Re-add the label you recorded in [Inventory your mesh](#inventory-your-mesh): `kubectl label namespace istio-injection=enabled`, or `kubectl label namespace istio.io/rev=` | | Workloads restarted | Re-add the injection label, then `kubectl rollout restart deployment -n ` | | Old policies deleted | `kubectl apply -f istio-config-backup.yaml` | | $[prodname]-managed Istio installed | `kubectl delete istio.operator.tigera.io default`, then reinstall your self-managed control plane | From b05bc1e509ace5d6ef647b440b1a59449e7fe575 Mon Sep 17 00:00:00 2001 From: Seth Malaki Date: Wed, 23 Sep 2026 19:03:57 +0100 Subject: [PATCH 16/20] docs: warn that the managed istiod injects no sidecars --- .../compliance/istio/migrate-from-sidecar.mdx | 36 +++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx index ffa7d260fe..260393ab39 100644 --- a/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx +++ b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx @@ -182,7 +182,9 @@ Choose this pattern if your cluster has a small number of meshed namespaces, or Remove the self-managed control plane, install the $[prodname]-managed one, then migrate namespaces in batches over a longer period. -Pods that still have sidecars keep forwarding traffic using their cached configuration, but be aware of two constraints: +Pods that still have sidecars keep forwarding traffic using their cached configuration, but be aware of three constraints: + +* **Restarted pods lose their sidecar.** The $[prodname]-managed istiod does not inject sidecars. A pod that restarts in a namespace you have not migrated yet, for example after an eviction or a scale-up, starts with no sidecar and outside the ambient mesh. Its traffic is plaintext, and workloads that require `STRICT` mTLS refuse it. * **Certificates are not rotated.** After the self-managed istiod is gone, sidecars can no longer refresh their workload certificates. Istio's default certificate lifetime is 24 hours, so plan to finish the migration well inside that window. * **mTLS between sidecar and migrated workloads fails without a shared root.** If you did not copy a `cacerts` secret, sidecars trust the root CA of your old control plane, and zTunnel presents a certificate from the new one. Traffic between a not-yet-migrated workload and a migrated workload fails TLS verification unless you temporarily disable mTLS for those service pairs with a `DestinationRule`. @@ -207,6 +209,7 @@ kubectl get networkpolicies.projectcalico.org,globalnetworkpolicies.projectcalic ## Step 1: Remove the self-managed Istio control plane Remove the control plane components only. Leave the injected sidecars and your Istio configuration resources (`VirtualService`, `AuthorizationPolicy`, and so on) in place for now: existing pods continue to serve traffic with their cached configuration, and you still need those resources for rollback. +From this step on, no new pod in any namespace gets a sidecar. 1. Remove sidecar injection from the namespaces you are about to migrate, so that no new pod comes up with a sidecar: @@ -405,7 +408,15 @@ Repeat for each namespace. Namespaces you have not labeled are unaffected, subje ## Roll back -Every step is independently reversible. Roll back in the reverse order of the step you completed. +Roll back in the reverse order of the step you completed. + +:::note + +The $[prodname]-managed istiod does not inject sidecars. +Re-adding an injection label brings sidecars back only while a self-managed control plane is running. +If you have completed Step 1, first [go back to your self-managed control plane](#go-back-to-your-self-managed-control-plane), then re-add the labels and restart the workloads. + +::: | Last completed step | Rollback | | --- | --- | @@ -414,7 +425,7 @@ Every step is independently reversible. Roll back in the reverse order of the st | Sidecar injection removed | Re-add the label you recorded in [Inventory your mesh](#inventory-your-mesh): `kubectl label namespace istio-injection=enabled`, or `kubectl label namespace istio.io/rev=` | | Workloads restarted | Re-add the injection label, then `kubectl rollout restart deployment -n ` | | Old policies deleted | `kubectl apply -f istio-config-backup.yaml` | -| $[prodname]-managed Istio installed | `kubectl delete istio.operator.tigera.io default`, then reinstall your self-managed control plane | +| $[prodname]-managed Istio installed | See [Go back to your self-managed control plane](#go-back-to-your-self-managed-control-plane) | After a rollback that re-injects sidecars, confirm that pods show `2/2` containers and that traffic flows before you continue. @@ -424,6 +435,25 @@ Applying the full `istio-config-backup.yaml` restores your original resources, b ::: +### Go back to your self-managed control plane + +1. Delete the `Istio` custom resource: + + ```bash + kubectl delete istio.operator.tigera.io default + ``` + +1. Wait until nothing of it is left. The two control planes must never run at the same time. + + ```bash + kubectl get pods -n calico-system -l 'app in (istiod,ztunnel,istio-cni-node)' + kubectl get felixconfiguration default -o jsonpath='{.spec.istioAmbientMode}{"\n"}' + ``` + + Both commands must return nothing. + +1. Reinstall your self-managed control plane with the same version and options you used before. + ## What changes in observability after migration * **Metrics.** In sidecar mode, metrics are reported with `reporter="source"` and `reporter="destination"`. In ambient mode they are reported with `reporter="source"` from zTunnel and `reporter="waypoint"`. Update dashboards and alerting rules that filter on the reporter label. From 48599acc299ec984e33503a9c51298cab7f9f042 Mon Sep 17 00:00:00 2001 From: Seth Malaki Date: Wed, 23 Sep 2026 19:04:16 +0100 Subject: [PATCH 17/20] docs: keep istio-base and the Istio CRDs during the uninstall --- .../compliance/istio/migrate-from-sidecar.mdx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx index 260393ab39..a50053cd0d 100644 --- a/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx +++ b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx @@ -223,22 +223,23 @@ From this step on, no new pod in any namespace gets a sidecar. kubectl label namespace istio.io/rev- ``` -1. Uninstall the self-managed control plane, including the Istio CNI plugin: +1. Uninstall the self-managed control plane, including the Istio CNI plugin. Keep the Istio CRDs: they hold your `VirtualService`, `AuthorizationPolicy`, and other resources, and your rollback path needs them. + + With Helm, keep the `istio-base` release: ```bash - istioctl uninstall --purge + helm uninstall istio-cni -n istio-system + helm uninstall istiod -n istio-system ``` - With Helm: + With `istioctl`, pass the options you installed with: ```bash - helm uninstall istio-cni -n istio-system - helm uninstall istiod -n istio-system - helm uninstall istio-base -n istio-system + istioctl uninstall ``` - :::note - `istioctl uninstall --purge` also removes the Istio CRDs. The Tigera Operator installs the CRDs that the bundled Istio needs, but your backed-up `VirtualService`, `AuthorizationPolicy`, and other resources are deleted with them. Confirm that `istio-config-backup.yaml` is complete before you run this command. + :::warning + Do not use `istioctl uninstall --purge`. It removes the Istio CRDs, and every Istio resource in the cluster is deleted with them. Keep `istio-base` installed until you are sure you will not roll back. ::: 1. Confirm that no Istio control plane components or CNI plugin remain: @@ -453,6 +454,7 @@ Applying the full `istio-config-backup.yaml` restores your original resources, b Both commands must return nothing. 1. Reinstall your self-managed control plane with the same version and options you used before. + With Helm, run `helm upgrade --install` for `istio-base` first. Deleting the `Istio` custom resource also deleted the `istiod-default-validator` webhook, and `istio-base` puts it back. ## What changes in observability after migration From 5b617f1e28ae7635db0516f68070be6c45eaddd0 Mon Sep 17 00:00:00 2001 From: Seth Malaki Date: Wed, 23 Sep 2026 19:08:59 +0100 Subject: [PATCH 18/20] docs: move mesh-wide policies to the new root namespace --- .../compliance/istio/migrate-from-sidecar.mdx | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx index a50053cd0d..e8c51febec 100644 --- a/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx +++ b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx @@ -168,6 +168,26 @@ Changing the root under a running $[prodname]-managed istiod splits the mesh: ex ::: +### Move your mesh-wide policies + +A policy in Istio's root namespace applies to the whole mesh. +Your self-managed Istio uses `istio-system` as its root namespace. The $[prodname]-managed Istio uses `calico-system`. + +After Step 2, policies in `istio-system` apply only to `istio-system`. They are not deleted, and nothing reports the change. Some of them fail open: + +* A mesh-wide `AuthorizationPolicy` that denies traffic stops denying it in your other namespaces. +* A mesh-wide `AuthorizationPolicy` that allows traffic no longer denies everything else by default. +* A mesh-wide `PeerAuthentication` with `mode: STRICT` stops applying. Namespaces without their own `PeerAuthentication` accept plaintext. + +List the policies in your root namespace: + +```bash +kubectl get authorizationpolicy,peerauthentication,requestauthentication,telemetry,wasmplugin,envoyfilter -n istio-system +``` + +Before Step 2, re-create each one in `calico-system`. +Until Step 2, `calico-system` is not the root namespace and has no meshed workloads, so the copies have no effect yet. + These constraints shape the two supported cutover patterns. ### Pattern 1: single maintenance window (recommended) From 585ca30aaa5c4a6ab36f0a59db66eb5b2ac10996 Mon Sep 17 00:00:00 2001 From: Seth Malaki Date: Wed, 23 Sep 2026 19:09:31 +0100 Subject: [PATCH 19/20] docs: name the DSCP mark and check FelixConfiguration first --- .../compliance/istio/migrate-from-sidecar.mdx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx index e8c51febec..76b7187f30 100644 --- a/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx +++ b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx @@ -21,7 +21,7 @@ Sidecar meshes put an Envoy proxy next to every pod. That proxy consumes memory Istio Ambient Mode replaces per-pod sidecars with a per-node zTunnel proxy for Layer 4 mTLS, and optional Waypoint proxies for Layer 7 features. On $[prodname], that comes with three additional benefits: -* **Your network policies keep working.** $[prodname] ships an [enhanced zTunnel](about-istio-ambient.mdx#the-enhanced-ztunnel-proxy) that preserves the original destination port, so existing $[prodname] and Kubernetes network policies continue to match traffic exactly as they did before the traffic was encrypted. In upstream Istio Ambient Mode, all mesh traffic appears as port 15008 and policies have to be rewritten. +* **Your network policies keep working.** $[prodname] ships an [enhanced zTunnel](about-istio-ambient.mdx#the-enhanced-ztunnel-proxy) that preserves the original destination port, so existing $[prodname] and Kubernetes network policies continue to match traffic exactly as they did before the traffic was encrypted. To make this work, $[prodname] sets DSCP 23 on the first packet of each new connection between two mesh pods. You can change the value with `dscpMark` in the `Istio` custom resource. In upstream Istio Ambient Mode, all mesh traffic appears as port 15008 and policies have to be rewritten. * **The mesh lifecycle is managed for you.** The Tigera Operator installs and upgrades istiod, the Istio CNI plugin, and zTunnel, and CVEs are addressed on the regular $[prodname] patch release cadence. * **Flow visibility is unchanged.** Because ports and workload identity are preserved, $[prodname] flow logs and the flow visualizations in the web console keep showing service-to-service traffic the way they did before you enabled mTLS. @@ -41,6 +41,7 @@ On $[prodname], that comes with three additional benefits: | `Gateway` (ingress) | No change | | `PeerAuthentication` with `mode: DISABLE` | Not supported. Remove before migrating | | $[prodname] network policy | No change, as long as you do not deploy Waypoints. See [Network policy during and after migration](#network-policy-during-and-after-migration) | +| DSCP field on new connections between mesh pods | Set to 23 by $[prodname]. Change it with `dscpMark` in the `Istio` custom resource | ## Before you begin @@ -124,6 +125,14 @@ Layer 4 enforcement is not affected. zTunnel enforces L4 `AuthorizationPolicy` r kubectl get peerauthentication --all-namespaces -o yaml | grep -B5 'mode: DISABLE' ``` +1. Confirm that the Istio fields in your `FelixConfiguration` are empty. The Tigera Operator sets them itself. If you set either one by hand, the install stops with `felixconfig IstioAmbientMode modified by user` or `felixconfig IstioDSCPMark modified by user`. + + ```bash + kubectl get felixconfiguration default -o jsonpath='{.spec.istioAmbientMode}{"\n"}{.spec.istioDSCPMark}{"\n"}' + ``` + + Both lines must be empty. If they are not, remove the fields before Step 2. + :::note Waypoints and `HTTPRoute` need the Gateway API CRDs. From 4403608db0d00fbf4ac934aad5fc7eb8a2511641 Mon Sep 17 00:00:00 2001 From: Seth Malaki Date: Wed, 23 Sep 2026 19:22:00 +0100 Subject: [PATCH 20/20] docs: warn that the mesh config is not carried over --- .../compliance/istio/migrate-from-sidecar.mdx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx index 76b7187f30..6084161f4d 100644 --- a/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx +++ b/calico-enterprise/compliance/istio/migrate-from-sidecar.mdx @@ -197,6 +197,25 @@ kubectl get authorizationpolicy,peerauthentication,requestauthentication,telemet Before Step 2, re-create each one in `calico-system`. Until Step 2, `calico-system` is not the root namespace and has no meshed workloads, so the copies have no effect yet. +### Check your mesh config + +Your self-managed Istio keeps its mesh-wide settings in the `mesh` key of the `istio` config map: + +```bash +kubectl get configmap istio -n istio-system -o jsonpath='{.data.mesh}' +``` + +The `Istio` custom resource has no field for these settings. +The $[prodname]-managed istiod starts with the Istio defaults, and your settings are not carried over. +Compare every key in the output against the defaults. These cause the most trouble: + +* `trustDomain`: every workload identity changes, and `AuthorizationPolicy` rules that name a principal deny traffic they used to allow. +* `outboundTrafficPolicy: REGISTRY_ONLY`: goes back to `ALLOW_ANY`, so workloads can reach external hosts that were blocked. +* `extensionProviders`: `AuthorizationPolicy` resources with `action: CUSTOM` lose their external authorization provider. +* `accessLogFile`: mesh access logs stop. Traffic through a Waypoint still shows up in $[prodname] L7 logs. + +If you depend on any of these settings, resolve that before you migrate. + These constraints shape the two supported cutover patterns. ### Pattern 1: single maintenance window (recommended)