Istio Certified Associate common mistakes
Istio Certified Associate · Understand the traps and how to avoid them.
Running `istioctl install` without `--set profile` or an IstioOperator overlay, then being surprised that components such as ingressgateway or the demo addons are absent or that resource requests are production-sized.
Candidates memorise the profile names but not what each one deploys. The default profile omits the egress gateway and most addons, while demo enables high tracing and access-log volume; without reading the profile matrix they assume a failed install when the component was simply never scheduled to be deployed.
What to do instead
Before installing, list the profiles with istioctl profile list, dump one with istioctl profile dump demo, and diff two with istioctl profile diff default demo. State in your answer which profile you chose and why, then verify the result with istioctl verify-install and by checking that the expected Deployments exist in istio-system.
Treating the revision label and the `istio-injection=enabled` label as interchangeable, so a namespace is labelled for injection but pods come up with one container.
Injection is controlled by namespace labels read by the sidecar injector webhook, and the label key differs between a default (non-revisioned) install and a revisioned one. Candidates set istio-injection=enabled on a cluster installed with --revision canary, or set the revision label while the webhook is disabled, and then debug the wrong layer.
What to do instead
Match the label to the install: istio-injection=enabled for a default install, istio.io/rev=<revision> for a revisioned one, never both. Confirm the mutating webhook configuration exists and its namespaceSelector matches, then recreate the pod (injection happens only at pod creation) and check kubectl get pod -o jsonpath='{.spec.containers[*].name}'.
Writing a VirtualService with a `hosts` entry that does not match the service's fully qualified name or the request's Host header, then concluding routing is broken.
The host field is matched against the destination of the request as resolved by the sidecar, not against an arbitrary string. A short name resolves only within the same namespace, and a gateway-bound VirtualService must list the gateway's external host. Candidates copy examples verbatim and miss the namespace or the port suffix.
What to do instead
Use the fully qualified form reviews.prod.svc.cluster.local when crossing namespaces, and bind gateway VirtualServices to the external hostname clients actually send. Validate with istioctl analyze -n <ns> and confirm the route with istioctl proxy-config routes <pod> --name <port> -o json.
Applying a DestinationRule subset that selects on a label no pod carries, or omitting the subset entirely while a VirtualService routes to it, producing 503 responses.
Subsets are label selectors evaluated by the client sidecar against the endpoints Envoy knows about. A typo in the version label, or a subset defined in the wrong namespace relative to the VirtualService, yields an empty cluster. The 503 looks like an application failure but is Envoy reporting no healthy upstream.
What to do instead
Check the actual pod labels with kubectl get pods --show-labels, keep the DestinationRule in the same namespace as the VirtualService that references it, and inspect the generated clusters with istioctl proxy-config clusters <pod> -o json to confirm the subset has endpoints. istioctl analyze flags references to undefined subsets.
Assuming a Gateway resource alone exposes a service, without a VirtualService bound to that gateway, or binding to the wrong gateway name/namespace selector.
The Gateway configures the listener on the ingress deployment; the VirtualService supplies the routes. Candidates create the Gateway, curl the load balancer, get 404, and start debugging the service. The 404 comes from Envoy having no matching route, not from the backend.
What to do instead
Always pair Gateway with a VirtualService whose gateways field names the gateway (ns/gateway when cross-namespace) and whose hosts include the external host. Verify the listener exists with istioctl proxy-config listeners <ingress-pod> -n istio-system and the route with proxy-config routes.
Enabling STRICT mutual TLS cluster-wide first, then discovering that a workload without a sidecar (a Job, a metrics scraper, a legacy VM) can no longer reach anything, and rolling back in a panic.
PeerAuthentication in STRICT mode rejects plaintext, and any client outside the mesh is plaintext by definition. Candidates flip the mesh-wide policy before inventorying non-mesh clients, and the resulting connection resets are hard to attribute under time pressure.
What to do instead
Roll out per-namespace or per-workload PeerAuthentication in PERMISSIVE first, watch istio_requests_total with connection_security_policy for plaintext traffic, migrate or exempt the remaining clients, then switch to STRICT. Use a port-level portLevelMtls override for the specific port an external client needs.
Writing an AuthorizationPolicy with an empty `rules` list or a `{}` rule and expecting it to allow traffic, when it denies everything.
An AuthorizationPolicy with action: ALLOW and no rules matches no requests, so the default-deny takes over for any workload that has at least one ALLOW policy. Candidates add a policy to restrict one path and inadvertently lock down all other paths to the same workload.
What to do instead
Remember that once any ALLOW policy selects a workload, only explicitly matched traffic is permitted. Add rules for every path and method that must remain reachable, or scope the policy with a selector to a single workload. Test with istioctl x authz check <pod> to see the effective policies.
Diagnosing 503s or connection failures by reading application logs and pod events, without ever querying the Envoy configuration of the specific sidecar.
In the mesh, routing, mTLS and retries are enforced by Envoy, so the failure often leaves no application log at all. Candidates default to kubectl logs and kubectl describe, which show a healthy pod, and lose time looking in the wrong place.
What to do instead
Use the proxy-config family on the actual pod: istioctl proxy-config cluster|listener|route|endpoint <pod> -n <ns>, plus istioctl proxy-status to confirm the sidecar is synced with istiod. If config is stale, the problem is the control plane, not the route.
Ignoring the requirement that the sidecar intercepts only certain ports, and expecting traffic to a container port not in the capture range to be routed or secured by Istio.
The sidecar redirects inbound and outbound traffic based on port lists and exclusions, and some ports (such as those in the exclusion list or SSH) bypass the proxy entirely. Candidates test a service on an excluded port and conclude that mTLS or routing is broken, when the traffic never entered the proxy.
What to do instead
Check the proxy's captured ports with istioctl proxy-config listeners <pod> and review the traffic.sidecar.istio.io/excludeInboundPorts and includeInboundPorts annotations. Keep application ports inside the capture range, and use istioctl x describe pod <pod> to see which ports are actually managed.
Upgrading the control plane in place with a new `istioctl install` on the same revision and expecting workloads to pick up the new proxy, or skipping the canary revision workflow altogether.
The data plane only changes when pods are restarted, and an in-place control plane upgrade gives no rollback path if the new version misbehaves. Candidates upgrade, see old proxy versions in proxy-status, and assume the upgrade failed, or they lose the ability to revert.
What to do instead
Use revision-based canary upgrades: install the new version with --revision <new>, move namespaces by relabelling istio.io/rev, restart workloads, verify with istioctl proxy-status and istioctl analyze, then remove the old revision. Keep the previous revision installed until validation completes so rollback is a label change.