AWS Builder Center

Istio on EKS

Istio on EKS

1. What injection adds, and the chains it installs

istio-injection=enabled (or istio.io/rev=<revision>) on a namespace makes istiod's mutating admission webhook rewrite every pod created in it:
addedwhy
istio-init init containerinstalls iptables rules as root with NET_ADMIN and NET_RAW, then exits — or, with istio-cni, an istio-validation init container that only checks the rules are there
istio-proxy sidecarthe Envoy that carries traffic, running as UID 1337
volumes for config and identitythe SDS socket and the root certificate; the pod's own key never touches disk
The rules live in the pod's own network namespace, and the path through them is short:
1
2
3
4
inbound: PREROUTING -> ISTIO_INBOUND -> ISTIO_IN_REDIRECT -> REDIRECT :15006
outbound: OUTPUT -> ISTIO_OUTPUT -> owner uid/gid 1337 ? RETURN
-> destination localhost ? RETURN
-> ISTIO_REDIRECT -> REDIRECT :15001
Two details from that picture matter later. Ports 15090, 15021 and 15020 are excluded from inbound capture (istio-iptables -d), so metrics and health checks reach the proxy directly. And the owner 1337 → RETURN rule is the only thing stopping Envoy's own outbound connections from being redirected back into Envoy — section 4.
The ports worth memorising, because every debugging session ends at one:
portwherewhat it is
15001sidecaroutbound: everything the app sends
15006sidecarinbound: everything arriving for the app (split from 15001 in Istio 1.4 — older posts show one port for both)
15000sidecar, localhost onlyEnvoy admin: /config_dump, /clusters, /stats
15020sidecarpilot-agent: merged app-and-proxy metrics
15021sidecar, gateway/healthz/ready — the proxy's readiness, not the app's
15090sidecarEnvoy's raw Prometheus metrics
15008ztunnel, waypointHBONE, mTLS tunnel in ambient mode
15012istiodxDS and certificate signing for proxies
15017istiodthe injection and validation webhooks — see section 2
The one that costs an afternoon is 15021. The sidecar rewrites the pod's readiness probe to go through pilot-agent, so a pod can be Ready because Envoy is ready while the application behind it is not, and the reverse. When a Service has endpoints but returns errors, look here first — the same class of failure the runbook  records for an ALB returning 503 with pods that look healthy.
The proxy can also start after the application, so an app that calls AWS on startup — fetching IRSA credentials from STS, reading a secret — fails its first request because nothing is listening on 15001 yet. Set holdApplicationUntilProxyStarts, or run the proxy as a Kubernetes native sidecar (an init container with restartPolicy: Always) where your Istio version supports it.

2. The webhook port the default EKS module does not open

This is the first EKS-specific failure, and it happens before any pod is meshed. The Kubernetes API server calls istiod's webhook on the pod's port 15017. On EKS the API server runs in AWS's VPC and reaches pods through the node security group — and the terraform-aws-eks module (v20, as this repository pins) opens only ports 443, 10250, 4443, 6443, 8443 and 9443 from the cluster to the nodes. Install Istio on that cluster and every pod creation in a labelled namespace fails with a webhook timeout, or — with failurePolicy: Ignore on some resources — silently skips injection.
The fix is one rule:
1
2
3
4
5
6
7
8
9
10
11
12
13
module "eks" {
# ...
node_security_group_additional_rules = {
ingress_istiod_webhook = {
description = "Cluster API to istiod webhook"
protocol = "tcp"
from_port = 15017
to_port = 15017
type = "ingress"
source_cluster_security_group = true
}
}
}
Ambient's HBONE on 15008 is node-to-node, which the module's recommended ephemeral-port rule (1025–65535 between nodes) already allows. Istio's own documentation names 15017 only on its GKE page; for EKS it defers to the aws-samples/istio-on-eks repository and an eks platform profile (--set global.platform=eks with Helm), which sets chart options for EKS but not your security groups.

3. PodSecurity makes istio-cni mandatory — and istio-system privileged

istio-init needs NET_ADMIN and NET_RAW. PodSecurity baseline already forbids both, and restricted forbids more, so in any namespace enforcing either profile the injected pod is rejected. The Deployment reports a failure to create, and nothing in the mesh configuration explains why.
istio-cni is the resolution. It moves the iptables work into a CNI plugin run by a node agent, so the pod needs no elevated capabilities at all. Install it with the CNI chart before istiod, and tell istiod not to inject istio-init (pilot.cni.enabled=true, set for every revision). On EKS it is a chained plugin invoked after the VPC CNI — the VPC CNI still assigns the pod its real VPC address, and istio-cni adds the redirect rules afterwards.
The costs people meet:
  • The privilege moves, it does not disappear. The CNI node agent needs hostPath mounts, so its namespace (istio-system by default) must enforce the privileged profile. Your application namespaces become restricted-compatible because one system namespace is not.
  • A race at node start. A pod scheduled before the node agent is ready can start without redirection and bypass the mesh. The injected istio-validation container detects that and blocks startup; since 1.21 the agent's default repairPods mode then fixes the pod in place.
  • Application init containers run before the proxy. Any network call they make is lost. Istio's documented workaround is to run that init container as UID 1337 — which is the bypass in section 4, used on purpose. Scope it with excludeOutboundIPRanges instead where you can.
  • It is a DaemonSet, so it does not exist on Fargate — one of the concrete reasons the runtime layer  lists "no DaemonSets" as Fargate's defining constraint.
  • Upgrade it on its own. The CNI agent is a cluster singleton that works with control planes one minor version either side of it; the docs recommend upgrading it separately from canary istiod revisions.
This repository's whereami pods are written to pass restricted (runAsNonRoot, numeric UID, all capabilities dropped, RuntimeDefault seccomp), but the whereami namespace does not enforce a PodSecurity profile — the sibling lab cluster does, which is why kdebug.sh exists there. Adding pod-security.kubernetes.io/enforce: restricted to k8s/base/namespace.yaml is the step that makes istio-cni non-optional here.
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article