ICA Practice Tasks & Questions
The ICA is the odd one on this whole ladder: an online, remote-proctored sitting that mixes performance-based tasks at a real command line with multiple-choice items, in one two-hour block. Most study banks pick a lane — a pile of MCQs, or a pile of labs — and leave you to guess how the other half feels. This one doesn't. It holds six hands-on tasks (T1–T6), each with a worked solution and a command you actually run to prove it, and sixteen multiple-choice questions (Q1–Q16) with full option-by-option explanations — both split across the four official domains from the ICA blueprint in rough proportion to their weight. Work a domain's tasks cold and timed first, since that's the skill reading never builds; then work its questions. When both sets stop surprising you, move to the timed papers — Mock Exam · Set 1, Set 2 and Set 3.
Most tests are one kind of question, over and over. This one is two different games in the same hour. Half the time someone hands you a real toy robot with a real remote control and says "make it walk to the door" — and you have to actually do it, not just describe how. The other half, they show you a picture and four possible captions and ask which one is exactly right. Some kids are great at describing robots but freeze up with the actual remote control in their hands, and some kids are the opposite. This page makes you practise both games, in the same sitting, because that's the only way to find out which one you're secretly weaker at before the day it counts.
How this bank is built
☺ Like you're 10: Two piles, not one — a pile of "actually do it" and a pile of "pick the right answer" — and both piles are biggest where the real exam is biggest.
The ICA's four domains — Traffic Management 35%, Securing Workloads 25%, Installation/Upgrades/Configuration 20%, Troubleshooting 20% — come straight from the CNCF's published curriculum; see the blueprint for the full competency list and exam logistics. This bank mirrors that shape twice over: two tasks and six questions on Traffic Management, two tasks and four questions on Securing Workloads, one task and three questions each on Installation and Troubleshooting. Neither the exact task-to-question ratio nor the total item count on the real paper is published anywhere — the split here is this bank's own design, built to give you real reps in both formats without either pile growing so large it stops being an untimed, readable drill.
| Domain | Blueprint weight | Tasks here | Questions here |
|---|---|---|---|
| 🐦 Traffic Management | 35% | 2 — T1–T2 | 6 — Q1–Q6 |
| 🐢 Securing Workloads | 25% | 2 — T3–T4 | 4 — Q7–Q10 |
| 🦫 Installation, Upgrades & Configuration | 20% | 1 — T5 | 3 — Q11–Q13 |
| 🐘 Troubleshooting | 20% | 1 — T6 | 3 — Q14–Q16 |
| Total | 6 tasks | 16 questions | |
Do every task cold and time-boxed — five to ten minutes on a throwaway cluster with Istio already installed, no leftover manifests from a previous attempt — before you touch the worked solution. Reading YAML and recognizing it looks right is a completely different skill from producing it yourself against a clock, and the real exam only scores the second one. Do the questions untimed on your first pass, reading the full explanation even when you're right, then come back later and time yourself once the material has settled.
Traffic Management — 35%
☺ Like you're 10: This is the biggest pile because it's the biggest slice of the real test — moving traffic around, splitting it, and making sure it survives when something breaks.
Seven named competencies live here: ingress and egress, in-mesh routing, DestinationRule traffic policy, traffic shifting, connecting to external services, resilience (circuit breaking, failover, outlier detection, timeouts, retries) and fault injection. Two resources carry almost all of it — VirtualService decides where a request goes, DestinationRule decides what happens once it gets there — and the tasks below deliberately make you write both, together, more than once.
T1 · Split traffic 90/10 with a canary override, and make the losing side resilient
The reviews service runs two versions behind one Kubernetes Service — v1 (stable) and v2 (canary), distinguished by the pod label version. Product wants 90% of ordinary traffic on v1 and 10% on v2, but any request carrying the header x-canary: always should go to v2 regardless of the split. Separately, v1 has been flaky under load and needs circuit breaking so one bad instance can't take the whole subset down with it.
Your task:
- Write a
DestinationRuleforreviewsdefining subsetsv1andv2. - Add a
trafficPolicyon thev1subset: a connection pool capping HTTP requests, and outlier detection ejecting an endpoint after 5 consecutive 5xx responses. - Write a
VirtualServicethat routes header-matched traffic tov2first, then splits everything else 90/10.
Done when: curl -H "x-canary: always" always lands on v2; a loop of a hundred plain requests lands roughly 90 on v1 and 10 on v2; and istioctl proxy-config cluster <pod> --fqdn reviews.default.svc.cluster.local shows the outlier-detection settings attached to the v1 cluster.
Show the worked solution
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: reviews
namespace: default
spec:
host: reviews.default.svc.cluster.local
subsets:
- name: v1
labels: { version: v1 }
trafficPolicy: # ---- subset-level override: only v1 gets this ----
connectionPool:
http:
http1MaxPendingRequests: 32
maxRequestsPerConnection: 10
outlierDetection:
consecutive5xxErrors: 5
interval: 10s
baseEjectionTime: 30s
maxEjectionPercent: 50
- name: v2
labels: { version: v2 }
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: reviews
namespace: default
spec:
hosts: ["reviews"]
http:
- match: # first match wins — the header rule MUST come before the split
- headers:
x-canary: { exact: "always" }
route:
- destination: { host: reviews, subset: v2 }
- route: # fallback: everyone who didn't match above
- destination: { host: reviews, subset: v1 }
weight: 90
- destination: { host: reviews, subset: v2 }
weight: 10for i in $(seq 1 100); do
kubectl exec deploy/sleep -- curl -s -o /dev/null -w '%{http_code} ' \
http://reviews.default.svc.cluster.local/api/v1/reviews
done
kubectl exec deploy/sleep -- curl -s -H 'x-canary: always' \
http://reviews.default.svc.cluster.local/api/v1/reviews # always v2
istioctl proxy-config cluster deploy/sleep --fqdn reviews.default.svc.cluster.local -o jsonWhy: VirtualService.http[] is evaluated top-to-bottom and stops at the first match, so the header rule has to come first — put the weighted fallback first and the header override would never fire. Circuit breaking (connectionPool) and outlier detection can be set at the top level of trafficPolicy or nested inside one specific subset's own trafficPolicy; nesting it under v1 only, as done here, protects the flaky subset without capping v2's healthy traffic too. The two weights, 90 and 10, must sum to exactly 100 — Istio's config validation (and istioctl analyze) will flag a route whose weights don't.
T2 · Expose one service to the internet, and lock every other outbound call down
The storefront app needs to (a) be reachable from outside the cluster at shop.example.com, and (b) be allowed to call exactly one external API, payments.example.net, and nothing else external — no accidental calls to a stray SaaS endpoint, no data exfiltration path via an unlisted host.
Your task:
- Write a
Gatewaybound to the ingress gateway workload, listening on port 443 for hostshop.example.com. - Write a
VirtualServiceattaching thatGatewayand routing tostorefront. - Write a
ServiceEntryforpayments.example.net. - Set
outboundTrafficPolicy.mode: REGISTRY_ONLY, scoped narrowly to thestorefrontnamespace via aSidecarresource rather than mesh-wide.
Done when: external traffic reaches storefront through the gateway; a call from a storefront pod to payments.example.net succeeds; a call from that same pod to any other external host (e.g. example.com) fails outright rather than silently passing through.
Show the worked solution
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
name: storefront-gw
namespace: storefront
spec:
selector:
istio: ingressgateway # matches the ingress gateway Deployment's pod labels
servers:
- port: { number: 443, name: https, protocol: HTTPS }
hosts: ["shop.example.com"]
tls: { mode: SIMPLE, credentialName: shop-example-com-tls }
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: storefront
namespace: storefront
spec:
hosts: ["shop.example.com"]
gateways: ["storefront-gw"] # omit this and the rule only applies in-mesh, never to the gateway
http:
- route:
- destination: { host: storefront, port: { number: 8080 } }
---
apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
name: payments-api
namespace: storefront
spec:
hosts: ["payments.example.net"]
ports:
- { number: 443, name: https, protocol: TLS }
resolution: DNS
location: MESH_EXTERNAL
---
apiVersion: networking.istio.io/v1
kind: Sidecar
metadata:
name: default
namespace: storefront
spec:
outboundTrafficPolicy:
mode: REGISTRY_ONLY # anything not a known Service or ServiceEntry: blockedkubectl -n storefront exec deploy/storefront -- curl -sS -m 5 https://payments.example.net/health # OK kubectl -n storefront exec deploy/storefront -- curl -sS -m 5 https://example.com # blocked
Why: a VirtualService with no gateways field defaults to mesh, which means it applies only to in-mesh sidecars, never to a gateway — the single most common reason a freshly written ingress rule "does nothing." REGISTRY_ONLY can be set mesh-wide in MeshConfig, but scoping it to one namespace's Sidecar resource — as required here — avoids breaking egress for every other team on the same mesh while you roll this out. Without the ServiceEntry, REGISTRY_ONLY would also block the one call you actually need.
Traffic Management — multiple choice (Q1–Q6)
Q1. A VirtualService's route names destination.subset: canary. No DestinationRule for that host defines a subset called canary. What actually happens to a request matching that route?
- A. Istio silently falls back to routing across all pods, ignoring the subset name.
- B. The request fails — typically a 503 — because the named subset doesn't resolve to any cluster;
istioctl analyzeflags this as a configuration error. - C. Kubernetes' API server rejects the
VirtualServiceatkubectl applytime. - D. The
DestinationRuleis auto-created with an empty subset matching zero pods.
Show answer & explanation
Answer: B. A invents a fallback behavior Envoy doesn't have — an undefined subset isn't "ignored," it's simply not there to route to. C is false: subset names aren't cross-validated against other objects by the Kubernetes API server, so kubectl apply succeeds even though the live behavior is broken — which is exactly why this is a live-traffic bug and not a rejected manifest. D fabricates an auto-creation behavior that doesn't exist. A subset a VirtualService names but no DestinationRule defines is the single most common self-inflicted 503 in the whole domain, and it's precisely the kind of error istioctl analyze is built to catch before it reaches production.
Q2. A route under http[].route[] lists two destinations with weight: 60 and weight: 30. What is true of this configuration?
- A. Istio automatically normalizes the weights so they still sum to 100.
- B. The weights across all destinations in one route must sum to exactly 100; this route is invalid and
istioctl analyzewill flag it. - C. Envoy interprets the two numbers as a 2:1 ratio regardless of their sum, so this behaves identically to 66/33.
- D. Only the first destination's weight is honored; the second is ignored.
Show answer & explanation
Answer: B. A fabricates an auto-normalization feature Istio doesn't perform. C is a plausible-sounding guess about ratio-based interpretation that isn't how the field is specified — weights are percentages of 100, not arbitrary ratios. D invents a "first wins" rule with no basis. The specification is explicit that per-route weights must sum to 100; a route that doesn't is invalid configuration, which is exactly the class of mistake a linter is built to catch before it becomes a mystery in production traffic.
Q3. Fault injection (fault.delay / fault.abort), request timeout, and retries with perTryTimeout are all configured on which single resource?
- A.
DestinationRule - B.
Gateway - C.
VirtualService - D.
Sidecar
Show answer & explanation
Answer: C. DestinationRule (A) carries connection-pool circuit breaking and outlier detection — resilience of a different kind, applied after a destination is chosen. Gateway (B) only configures listener ports, protocols and TLS at the mesh edge. Sidecar (D) scopes what a proxy can see and reach, not per-request behavior. Fault injection, timeouts and retries are all about what happens to a specific request as it's routed, which is squarely VirtualService territory — the same object that owns matching and weighted routing.
Q4. A Sidecar resource sets outboundTrafficPolicy.mode: REGISTRY_ONLY for a namespace, and a ServiceEntry exists for api.partner.com. What does this combination achieve?
- A. All outbound traffic from that namespace is automatically encrypted with mTLS, regardless of destination.
- B. Calls to any host not represented by a known Kubernetes Service or a
ServiceEntry— such as an undeclared external API — are blocked, while calls toapi.partner.comare allowed through. - C. Istio automatically discovers and registers every external host the workload has ever called.
- D. Outbound calls are rate-limited to the QPS configured on the
ServiceEntry.
Show answer & explanation
Answer: B. A conflates two unrelated concerns — mTLS is a PeerAuthentication/DestinationRule matter, not something REGISTRY_ONLY touches. C invents an auto-discovery capability that's the opposite of the point: REGISTRY_ONLY exists specifically to stop undeclared destinations from working. D fabricates a rate-limiting behavior ServiceEntry doesn't have. The actual effect is an allow-list: only hosts Istio already knows about — in-mesh Services or explicitly declared ServiceEntry hosts — are reachable; everything else is refused, which is the whole mechanism behind locking down egress.
Q5. Where does locality-aware failover (localityLbSetting) live, and what else must be configured for it to actually shift traffic?
- A. On the
VirtualService, as a sibling ofroute; it requires no other configuration. - B. On the
DestinationRule'strafficPolicy, nested insideloadBalancer; it requiresoutlierDetectionto be configured before it will shift anything. - C. On the
Gateway, as a top-level field; it requires a multi-cluster mesh to take effect. - D. On the
Sidecarresource; it requiresoutboundTrafficPolicy.mode: REGISTRY_ONLY.
Show answer & explanation
Answer: B. A and C both misplace the field on the wrong resource entirely — locality failover is a load-balancing concern, which lives on DestinationRule, not routing (VirtualService) or edge listeners (Gateway). D pairs it with an unrelated egress-lockdown field. The two-part trap here is real: the field nests inside loadBalancer, not beside it, and without outlierDetection configured to actually detect an unhealthy locality, there's nothing to trigger the failover — the setting sits inert.
Q6. An ingress VirtualService is written with no gateways field at all. What is the practical effect?
- A. It applies to every
Gatewayobject in the same namespace automatically. - B. It defaults to
mesh, so it governs only in-mesh, sidecar-to-sidecar traffic — external requests arriving through any gateway ignore it entirely. - C.
kubectl applyrejects the object as incomplete. - D. It applies to the default ingress gateway only, and no other.
Show answer & explanation
Answer: B. A fabricates an automatic namespace-wide binding that doesn't exist — gateway attachment is always explicit, never implicit-by-namespace. C is false; the field is optional, not required, so the object validates fine. D invents a "default gateway" fallback where none is specified. The real default is the special value mesh: without an explicit gateways list, the rule governs only sidecar-to-sidecar traffic inside the mesh, which is precisely why a newly written ingress rule that "does nothing" is one of the most common early mistakes in this domain.
Securing Workloads — 25%
☺ Like you're 10: Locks come in two kinds here — one checks whether you're wearing the right uniform, the other checks whether your ID badge is real — and a test question loves to mix them up on purpose.
Three competencies, and precision about which resource answers which question is most of what's tested. PeerAuthentication asks "must the caller present a mesh certificate?" — workload identity. RequestAuthentication asks "is this end-user's token valid?" — and on its own it only rejects invalid tokens, never absent ones. AuthorizationPolicy asks "is this specific caller allowed to do this specific thing?"
T3 · Ratchet a namespace to strict mTLS without breaking one legacy health check
The legacy namespace has been running with permissive mTLS while every workload was migrated onto the mesh. Migration is done, except one thing: an external load-balancer health probe, outside the mesh entirely, still calls legacy-api's /healthz on port 9000 in plain HTTP, and that call must keep working. Everything else in the namespace is now mesh-to-mesh and should require mTLS.
Your task:
- Set the namespace-wide default
PeerAuthenticationforlegacytoSTRICT. - Add a workload-specific exception so
legacy-api's port 9000 alone stays permissive, without weakening mTLS on any of its other ports.
Done when: a plaintext curl to legacy-api:9000/healthz from outside the mesh still returns 200; a plaintext call to any other port on legacy-api, or to any other workload in the namespace, is refused.
Show the worked solution
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: default # the reserved name for a namespace-wide policy
namespace: legacy
spec:
mtls:
mode: STRICT
---
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: legacy-api-health-exception
namespace: legacy
spec:
selector: # portLevelMtls is only valid on a selector-scoped policy
matchLabels: { app: legacy-api }
mtls:
mode: STRICT
portLevelMtls:
9000:
mode: PERMISSIVE # health check on 9000 only; every other port stays STRICTcurl -sS -m 5 http://legacy-api.legacy.svc.cluster.local:9000/healthz # 200, plaintext OK curl -sS -m 5 http://legacy-api.legacy.svc.cluster.local:8080/api # refused - STRICT applies
Why: portLevelMtls is only meaningful on a PeerAuthentication that carries a selector — the namespace-wide "default" policy has no ports of its own to override, since it isn't scoped to one workload's container ports. Writing a second, workload-specific policy alongside the namespace default, rather than weakening the default itself, is what keeps the exception narrow: one port, on one workload, instead of the whole namespace falling back to permissive while you sort out one health check.
T4 · Require BOTH the right service identity and a valid token — not either one
The orders service should only accept calls that satisfy two conditions at once: the caller must be the web-frontend ServiceAccount, and the request must carry a JWT issued by https://accounts.example.com. A first draft of the policy technically applies both checks, but testing shows a request with a stolen, unrelated ServiceAccount token gets through as long as it carries any valid JWT — the AND was accidentally written as an OR.
Your task:
- Write a
RequestAuthenticationfor the issuer and its JWKS endpoint. - Write an
AuthorizationPolicythat genuinely requires both the ServiceAccount identity and the JWT, correcting the AND/OR mistake.
Done when: a call from web-frontend's identity with a valid JWT succeeds; a call from any other identity with a valid JWT is denied; a call from web-frontend's identity with no JWT is denied.
Show the worked solution
apiVersion: security.istio.io/v1
kind: RequestAuthentication
metadata:
name: orders-jwt
namespace: prod
spec:
selector:
matchLabels: { app: orders }
jwtRules:
- issuer: "https://accounts.example.com"
jwksUri: "https://accounts.example.com/.well-known/jwks.json"
---
# THE BROKEN FIRST DRAFT — two separate `- source:` entries ORs them together:
# rules:
# - from:
# - source: { principals: ["cluster.local/ns/prod/sa/web-frontend"] }
# - source: { requestPrincipals: ["https://accounts.example.com/*"] }
# ...lets ANY valid-JWT caller through, identity check or not.
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: orders-require-identity-and-jwt
namespace: prod
spec:
selector:
matchLabels: { app: orders }
action: ALLOW
rules:
- from:
- source: # ONE source entry: both keys inside it are ANDed
principals: ["cluster.local/ns/prod/sa/web-frontend"]
requestPrincipals: ["https://accounts.example.com/*"]Why: keys written inside one source block are ANDed together; separate - source: list entries are ORed. The broken draft split principals and requestPrincipals into two entries, which silently turned "this identity and a valid token" into "this identity or a valid token" — a real hole, not a cosmetic bug, and the exact trap the curriculum's own troubleshooting material calls out by name. The fix is purely structural: one source, both keys inside it.
Securing Workloads — multiple choice (Q7–Q10)
Q7. A workload has PeerAuthentication set to STRICT and no RequestAuthentication at all. A request arrives with no JWT. What happens?
- A. It's denied —
STRICTmTLS alone also requires a valid JWT on every request. - B. It's allowed through, as far as authentication goes —
STRICTmTLS only enforces that the caller presents a valid mesh certificate; with noRequestAuthenticationconfigured, nothing checks for a JWT at all. - C. It's denied because
AuthorizationPolicyalways requires a JWT by default. - D. The request hangs until a JWT is supplied, since Istio blocks on a missing token.
Show answer & explanation
Answer: B. A conflates two independent checks that the exam deliberately tests as separate: workload identity (PeerAuthentication) and end-user identity (RequestAuthentication). C invents a default behavior AuthorizationPolicy doesn't have — with no policy selecting the workload at all, nothing about JWTs is enforced. D fabricates a hang; there's no such blocking behavior. STRICT mTLS is entirely about the mesh certificate between proxies; requiring a JWT is a separate, opt-in layer that only exists once you add a RequestAuthentication and an AuthorizationPolicy rule that actually checks requestPrincipals.
Q8. A namespace has one AuthorizationPolicy with action: ALLOW selecting the billing workload, permitting calls only from sa/reporting. A completely unrelated workload in the same namespace, notifications, has no AuthorizationPolicy selecting it at all. What can reach notifications?
- A. Nothing — once any
AuthorizationPolicyexists anywhere in the namespace, every workload in that namespace becomes default-deny. - B. Anything —
notificationsis unaffected, since default-deny is triggered only for a workload actually selected by an ALLOW policy, and this one selects onlybilling. - C. Only
sa/reporting, inheriting the same rule asbilling. - D. Nothing, because
AuthorizationPolicyobjects are cluster-scoped by default.
Show answer & explanation
Answer: B. A is the common overgeneralization — the default-deny effect is scoped to the specific workload an ALLOW policy selects, not to every workload sharing its namespace. C invents an inheritance mechanism that doesn't exist between unrelated workloads. D is simply false — an AuthorizationPolicy with no explicit namespace override is namespace-scoped, and even a mesh-wide one wouldn't "inherit" rules per-workload the way this option implies. The precise rule — default-deny triggers per selected workload, not per namespace — is exactly the nuance that separates "this policy is scoped correctly" from "I just locked out a service I never meant to touch."
Q9. An AuthorizationPolicy's rules[0].from lists two separate entries: - source: { principals: [...] } and - source: { requestPrincipals: [...] }. What does this actually enforce?
- A. The caller must satisfy both the identity check and the token check — a strict AND.
- B. The caller must satisfy either the identity check or the token check — an OR, meaning a valid JWT alone is enough even from an unexpected identity.
- C. This is invalid YAML and the policy will fail to apply.
- D. Only the first entry is evaluated; the second is silently ignored.
Show answer & explanation
Answer: B. A is the mistake this exact structure produces in practice — it looks like an AND to someone skimming the YAML, but it isn't one. C is false; this is syntactically valid and Istio applies it exactly as written, which is what makes the bug dangerous — nothing errors, it just isn't the policy the author intended. D fabricates a "first wins" behavior. Separate - source: entries in a list are ORed; only keys living inside the same source block are ANDed. This is precisely the T4 mistake, tested from the reading side instead of the writing side.
Q10. A Gateway terminates TLS with tls.mode: SIMPLE and credentialName: shop-tls. In which namespace must the shop-tls Secret live for this to work with Istio's default SDS setup?
- A. Any namespace —
credentialNameis a cluster-wide reference. - B. The same namespace as the
Gatewayobject's own workload — typically the ingress gateway's namespace, such asistio-system. - C. The namespace of the backend service the
VirtualServiceultimately routes to. - D. The
defaultnamespace, always, regardless of where anything else is deployed.
Show answer & explanation
Answer: B. A fabricates a cluster-wide lookup that doesn't reflect how the gateway's SDS setup resolves the Secret. C confuses the TLS-terminating hop (the gateway) with an entirely different hop (the backend), which have no required relationship to each other's namespace. D invents a fixed namespace with no basis. The Secret has to live alongside the gateway proxy that actually needs to read it — the workload's own namespace — which is a detail that trips people up specifically because the Gateway object itself is often defined in the application's namespace while the gateway workload runs in a platform namespace like istio-system.
Installation, Upgrades & Configuration — 20%
☺ Like you're 10: Two ways to build it, two ways to update it — and the test wants to know you didn't just memorize one path and hope the other never comes up.
Four competencies: installing with istioctl or Helm, installing in sidecar or ambient mode, customizing the installation, and upgrading canary or in-place. The task below drills the canary path specifically, because it's the one people skip in real life — most teams upgrade in place, under time pressure, and never get the reps a graded exam expects.
T5 · Run a canary control-plane upgrade and prove it landed, without touching anything untested
Production is running istiod at revision 1-26-0, referenced by every namespace via the plain istio-injection=enabled label. A new Istio version needs to reach one low-risk namespace, staging, first — fully verified — before anything else moves.
Your task:
- Install a second control plane under revision
1-27-0, alongside the existing one. - Move only
stagingonto it, and restart its workloads. - Verify every proxy in
stagingis actually talking to the new control plane before calling it done.
Done when: istioctl proxy-status shows every proxy in staging reporting SYNCED against the 1-27-0 control plane, and every other namespace is untouched and still on 1-26-0.
Show the worked solution
# 1. Install the new revision alongside the old one - nothing moves yet istioctl install --set revision=1-27-0 -y # 2. staging currently has BOTH istio-injection=enabled (implicit, from the old default) # AND is about to get istio.io/rev - the plain label wins if both are present, # so remove it explicitly before adding the revision label. kubectl label namespace staging istio-injection- kubectl label namespace staging istio.io/rev=1-27-0 # 3. Injection only happens at pod creation - existing pods are never retrofitted kubectl rollout restart deployment -n staging # 4. Verify BEFORE calling it done - don't trust the label change alone istioctl proxy-status | grep staging # every row: SYNCED, istiod-1-27-0-... # Only after staging is fully verified would a stable tag move a whole fleet: istioctl tag set prod-stable --revision 1-27-0 kubectl label ns staging istio.io/rev- istio.io/tag=prod-stable --overwrite
Why: if a namespace carries both istio-injection=enabled and istio.io/rev, the plain injection label takes precedence — a detail that silently keeps a namespace on the default revision even after you've added a revision label, unless you remove the plain one first. Injection is evaluated only when a pod is created, so a label change alone does nothing to already-running pods; skipping the restart is the most common reason a "completed" canary migration turns out to have moved nothing. proxy-status is the actual proof — a namespace label says what should be true, the sync status says what is.
Installation & Configuration — multiple choice (Q11–Q13)
Q11. Installing Istio via Helm uses three charts. What is the required order?
- A.
istiod, thenbase, thengateway— the control plane must exist before its CRDs. - B.
base(CRDs and cluster roles), thenistiod, thengatewayper ingress or egress gateway. - C. Order doesn't matter — Helm resolves any dependency ordering automatically across the three charts.
- D.
gateway, thenbase, thenistiod.
Show answer & explanation
Answer: B. A reverses a real dependency — istiod depends on CRDs that base installs, so installing it first would fail or leave it degraded. C overstates Helm's own capabilities; these are three independently installed releases, not one chart graph Helm resolves for you. D is a different wrong ordering with the same underlying problem as A. base must land first because everything else depends on its CRDs and cluster roles; gateway comes last because it's optional and per-gateway, unlike the control plane it depends on.
Q12. Which statement correctly distinguishes an in-place upgrade from a canary upgrade?
- A. In-place replaces the running control plane directly, all at once; canary installs a second
istiodunder a revision and moves namespaces onto it one at a time via a label, so rollback is just relabelling. - B. Canary and in-place are two names for the same underlying mechanism, differing only in CLI flag.
- C. In-place requires a revision label on every namespace; canary does not.
- D. Canary can only be performed with Helm; in-place can only be performed with
istioctl.
Show answer & explanation
Answer: A. B collapses a distinction the curriculum names as two separate competencies precisely because the operational risk profile differs enormously. C reverses which strategy actually uses revision labels — that's canary's whole mechanism, not in-place's. D invents a tooling restriction that doesn't exist; both strategies are achievable with either installer. The real distinction is blast radius and rollback: in-place is fast but all-or-nothing, canary is slower but lets you verify one namespace, at low risk, before anything else moves — and undoing it is as simple as moving the label back.
Q13. A namespace carries both the label istio-injection=enabled and the label istio.io/rev=1-27-0 at the same time. Which one actually governs sidecar injection for new pods?
- A.
istio.io/revalways wins, since it's the more specific label. - B.
istio-injection=enabledwins; the revision label is ignored while the plain label is present. - C. Both apply simultaneously, and the pod gets two sidecars.
- D. Neither applies — having both present disables injection entirely as a safety measure.
Show answer & explanation
Answer: B. A assumes specificity determines precedence, which sounds reasonable but isn't how Istio actually resolves the conflict. C fabricates a double-injection outcome that doesn't happen. D invents a safety fallback that doesn't exist either. The plain istio-injection=enabled label takes precedence over any istio.io/rev label on the same namespace — which is exactly why T5's worked solution removes the plain label explicitly before adding the revision one; skipping that step is a quiet way to run a "successful" canary migration that moved nothing at all.
Troubleshooting — 20%
☺ Like you're 10: Something's broken and you have three places to look — did you write the config wrong, is the brain of the mesh confused, or does one specific messenger not know what you think it knows? Check them in that order.
Three named competencies, and they name a ladder: configuration, the control plane, the data plane. istioctl analyze catches most configuration mistakes before they cause a symptom. istioctl proxy-status tells you whether the control plane's version of the truth actually reached every proxy. istioctl proxy-config asks one specific proxy what it currently believes — which is sometimes different from what you'd expect even when the first two steps look clean.
T6 · A routing change causes 503s that analyze doesn't catch
Someone rolled out a new subset, v3, for the checkout service. The DestinationRule already defines a v3 subset, and the VirtualService already routes 20% of traffic to it. istioctl analyze reports no problems. Requests to v3 still fail with 503 UF, upstream connect error... NO_HEALTHY_UPSTREAM.
Your task: find the actual root cause and fix it — without touching the VirtualService or DestinationRule, both of which are already correct.
Done when: istioctl proxy-config endpoint for the v3 cluster lists at least one healthy endpoint, and a request routed to v3 returns 200.
Show the worked solution
# 1. CONFIGURATION rung - already clean, per the prompt, but confirm it yourself
istioctl analyze -n prod
# 2. CONTROL PLANE rung - is the config actually reaching proxies?
istioctl proxy-status | grep checkout # SYNCED - so the control plane isn't the problem
# 3. DATA PLANE rung - what does the cluster actually resolve to?
istioctl proxy-config cluster checkout-7c9d-abcde.prod \
--fqdn checkout.prod.svc.cluster.local --port 8080 -o json
# -> finds a cluster named "outbound|8080|v3|checkout.prod.svc.cluster.local"
istioctl proxy-config endpoint checkout-7c9d-abcde.prod \
--cluster "outbound|8080|v3|checkout.prod.svc.cluster.local"
# -> ZERO endpoints listed. The cluster exists; nothing backs it.
# The actual cause: the DestinationRule's v3 subset selects { version: v3 },
# but the Deployment's pod template still carries { version: v3-rc1 }.
kubectl -n prod get pods -l app=checkout --show-labels
kubectl -n prod patch deployment checkout-v3 --type=json \
-p '[{"op":"replace","path":"/spec/template/metadata/labels/version","value":"v3"}]'
kubectl -n prod rollout status deployment/checkout-v3
istioctl proxy-config endpoint checkout-7c9d-abcde.prod \
--cluster "outbound|8080|v3|checkout.prod.svc.cluster.local" # now lists healthy endpointsWhy: this is the specific trap that separates two similar-looking 503s. A subset the VirtualService names but the DestinationRule never defines is a config-level mistake, and istioctl analyze catches it. A subset that is correctly defined but whose label selector matches zero live pods is a live-state mismatch — the Envoy cluster is created correctly, it's simply empty — and no static linter can see that, because nothing about it is invalid YAML. NO_HEALTHY_UPSTREAM specifically means "the cluster exists, it just has no endpoints," which is the signal that should send you straight to proxy-config endpoint rather than back to re-reading the manifest.
"From my side of things, every one of these failures just looks like '503, please retry.' What actually tells me which rung of the ladder I'm on is the text after the 503: NO_HEALTHY_UPSTREAM sends the platform team to a label selector, RBAC: access denied sends them to a policy, and a plain connection reset usually means the sidecar itself isn't there yet. I've learned to paste the whole error, not just '503', into any bug I file — it turns a twenty-minute investigation into a two-minute one for whoever's on call."
Troubleshooting — multiple choice (Q14–Q16)
Q14. Put the three troubleshooting rungs in the order the curriculum names them, and match each to its primary command.
- A. Data plane (
proxy-config) → Control plane (proxy-status) → Configuration (analyze). - B. Configuration (
analyze) → Control plane (proxy-status) → Data plane (proxy-config). - C. Control plane (
proxy-status) → Data plane (proxy-config) → Configuration (analyze). - D. There is no defined order; any rung may be checked first with equal efficiency.
Show answer & explanation
Answer: B. A and C both reorder the ladder in ways that cost time in practice — checking one specific proxy's live config before confirming the control plane even pushed anything to it (or before ruling out a plain YAML mistake) means redoing work once the real cause surfaces upstream. D is true in the loose sense that nothing forces the order, but it ignores why the order the curriculum gives is the efficient one: static config mistakes are the cheapest to rule out, so check them first; a control-plane sync problem affects every proxy, so it's the next cheapest broad check; and one proxy's live belief is the most specific, most expensive-to-gather signal, so it comes last.
Q15. In istioctl proxy-status output, one proxy's row shows STALE in the CDS column, and another shows NOT SENT. What does each mean?
- A. Both mean the same thing — the proxy is out of date and needs a restart.
- B.
STALEmeans istiod pushed an update but the proxy hasn't acknowledged consuming it yet;NOT SENTmeans istiod currently has nothing new to send that proxy at all. - C.
STALEmeans the proxy has crashed;NOT SENTmeans the proxy was never injected. - D.
STALEapplies only toistioditself;NOT SENTapplies only to individual workloads.
Show answer & explanation
Answer: B. A erases a distinction that changes what you do next — a stuck push (STALE) points you at the control plane's delivery path, while NOT SENT is often entirely benign, meaning there's simply nothing new for that proxy. C and D both invent failure modes and scoping rules that aren't what these two words actually mean in proxy-status output. Reading this column correctly is what tells you whether "the control plane rung" of the ladder is actually a problem, or whether you're looking at a proxy with nothing new to report.
Q16. A namespace is labelled istio-injection=enabled, but a pod that was already Running before the label was applied still shows no istio-proxy container. What is the most likely cause?
- A. The CNI plugin is misconfigured and is blocking sidecar attachment.
- B. Injection is evaluated only at pod creation time; a pod that existed before the label was added is never retrofitted, and needs a rollout restart.
- C. The Istio version on that node doesn't support automatic injection.
- D. The namespace label was applied to the wrong object — labels must go on the Deployment, not the namespace.
Show answer & explanation
Answer: B. A and C both invent infrastructure failures with no supporting evidence in the scenario — nothing points at the CNI or a version mismatch. D is simply wrong about where the label belongs; namespace-level injection is exactly how it's meant to work. The actual behavior is by design: the mutating webhook that adds the sidecar only fires when a pod is created, so any pod already running when the label lands keeps running exactly as it was until something recreates it — which is why "I labelled the namespace and nothing changed" is one of the most common early support questions, and why every install and upgrade workflow in this domain ends with a restart.
Running this bank like exam day
☺ Like you're 10: First time through, go slow and read everything. Once nothing here surprises you anymore, do it again with a clock running, because the real test has one.
Two passes, not one. On the first pass, work every task cold before opening its solution, and read every question's full explanation even when you got it right — the goal is coverage, not speed. On the second pass, once the material has settled, time yourself: five to ten minutes per task, roughly ninety seconds per question, and treat the whole bank as one 45–60 minute sitting with only istio.io open in another tab — because that's the one resource a real hands-on Linux Foundation exam typically allows, and finding the right page under pressure is its own exam skill.
On a throwaway cluster with Istio already installed: do T1 and T2 back to back, ten minutes each, no peeking. Then T3 and T4, same rules. Then T5 if you have a spare revision to install, or read it closely if you don't. Only once all six are attempted — not necessarily solved — open every worked solution and compare, line by line, against what you actually wrote. The gap between your attempt and the solution is worth more than the eleven questions you'll answer correctly by pattern-matching alone.
| Task result | What it means | What to do |
|---|---|---|
| Clean, under time | "Done when" passed first try, docs used only to confirm a field name. | Nothing — move on, this competency is solid. |
| Correct, over time | You got there, but hunted for syntax or a CLI flag along the way. | Drill the exact command again tomorrow, cold, with a stopwatch. |
| Wrong resource or field | You reached for DestinationRule when the answer needed VirtualService, or similar. | Re-read the blueprint section for that competency before retrying. |
| Blank | You didn't know where to start. | Read the worked solution slowly, then retry the same task cold in 24 hours. |
Unlike some Linux Foundation exams, the ICA's exact task count, its multiple-choice item count, and its pass mark are not published on the official listing — the six-and-sixteen split in this bank is this study resource's own design, not a stated fact about the real paper. This site is an independent, unofficial study resource and is not affiliated with the CNCF or The Linux Foundation. Before you register or pay for anything, read the official Linux Foundation ICA page and the current candidate handbook for the real duration, price, retake policy, permitted documentation and pinned Istio version — see the ICA blueprint for the fuller logistics table.
Benny: T1 took me four minutes. T5 took me twenty-five, and I still had to peek at the label thing.
Professor Owl: That's useful information, not a failure. Traffic management is muscle memory for you already. Canary upgrades aren't — most people's aren't, because most people upgrade in place under pressure and never get the reps.
Remy: I did all sixteen questions in eleven minutes. Fourteen right!
Timmy: Which two, and can you explain the other three options on each?
Remy: …Q9 and Q13. Give me a minute.
Gizmo: Fourteen out of sixteen is basically an A. Skip straight to the mock exam, you've clearly got the questions down. 😈
Benny: Questions aren't the whole exam, Gizmo. She hasn't touched a terminal yet today.
Professor Owl: Do T3 and T4 before you sleep tonight, Remy. Security is the domain where "I know the answer" and "I can write the policy correctly under a clock" turn out to be two different skills.
1. Why does this bank hold six tasks and sixteen questions rather than an even split across all four domains? 2. In T1, why must the header-matched route come before the weighted fallback in the VirtualService's http[] list? 3. What's the structural difference between an AuthorizationPolicy that correctly ANDs an identity and a JWT, versus one that accidentally ORs them? 4. In T6, why doesn't istioctl analyze catch the actual root cause of the 503? 5. If a namespace has both istio-injection=enabled and istio.io/rev set, which one governs injection? 6. Name the three troubleshooting rungs in order, and the command that belongs to each. 7. What exam-day detail about the ICA does this page explicitly refuse to state as fact, and why?
Check your answers
- It mirrors the ICA's own domain weights (35/25/20/20) applied separately to both the task pile and the question pile, so Traffic Management — the heaviest domain — gets the most practice in both formats rather than an arbitrary even split.
VirtualService.http[]matches are evaluated top-to-bottom and stop at the first match. Putting the weighted fallback first would mean it always matches before the engine ever reaches the header rule, so the canary override would never fire.- Keys written inside one
- source:block are ANDed; separate- source:list entries underfromare ORed. SplittingprincipalsandrequestPrincipalsinto two entries silently turns "identity and token" into "identity or token" — a real security hole, not a cosmetic difference. - Because the
DestinationRuleandVirtualServiceare both syntactically valid and internally consistent — the subset really is defined and really is referenced correctly. The failure is a live-state mismatch (the pod's actual label doesn't match the subset's selector), which is invisible to a static linter and only shows up by asking the running proxy what it currently sees, viaproxy-config endpoint. - The plain
istio-injection=enabledlabel wins overistio.io/revwhen both are present on the same namespace. - Configuration →
istioctl analyze. Control plane →istioctl proxy-status. Data plane →istioctl proxy-config(cluster/endpoint/route/listener/secret) and theistio-proxylogs. - The exact task count, question count and pass mark — none of these are published on the official Linux Foundation ICA listing, so this page states its own bank's split honestly as a study-resource design choice rather than presenting invented numbers as if they described the real exam. Confirm the real figures on the official page before you book.
Once every task and question here stops surprising you, move to the timed papers: Mock Exam · Set 1, Set 2 and Set 3. For the full domain breakdown and exam logistics, see the ICA blueprint; for a day-by-day route through all of it, see the ICA study plan. If a mesh is genuinely part of your platform work rather than just this badge, Platform Engineering's own ICA page places it inside the wider CNPE picture, and the Istio tool guide on this course covers the same resources at lesson pace rather than drill pace. Everything here assumes Kubernetes fluency the ICA itself doesn't teach — if Service, Deployment and label selectors aren't already second nature, a CKA-level foundation belongs before this bank, not after it.