TT Lab
Get started
Learn Learning paths Courses

ICA — Istio Certified Associate

Gateway Opens the Door; VirtualService Lays the Path

Continue in TT Lab

In one line

A Gateway decides only with which ports, hosts, and TLS to receive traffic, and all routing after it is received is done by a VirtualService. What connects the two is a single line, the VirtualService's gateways field.

Why this was needed

Kubernetes Ingress mixed the entry point and routing in a single resource and lacked expressiveness. Requirements such as header-based routing, weighted distribution, and TLS passthrough mode all flowed into per-controller annotations, and as a result manifests became tied to a specific ingress controller.

Istio solved this problem with a separation of responsibilities. A Gateway declares the listeners of the gateway Pods, and for routing it reuses as is the VirtualService already used inside the mesh. Thanks to this, "internal east-west routing" and "north-south routing coming in from outside" can be written in the same syntax.

How it works

A Gateway uses spec.selector to choose which gateway Pods to attach this configuration to. It is usually istio: ingressgateway. A Gateway resource does not create a proxy. It merely layers the listener configuration onto a gateway Deployment that is already running.

You must tell the three TLS modes apart.

Mode What the gateway does What is needed
SIMPLE Terminates TLS and forwards in plaintext to the backend A server certificate (credentialName)
MUTUAL TLS termination + client certificate verification A server certificate + a CA
PASSTHROUGH Does not terminate and forwards looking only at the SNI No certificate needed

For PASSTHROUGH you must write the protocol as TLS, not HTTPS. This is because the gateway does not parse HTTP, and so in this case routing is also done not with HTTP rules but with sniHosts in a tls block. The kubernetes.io/tls Secret that credentialName points to must be in the namespace where the gateway Pod is running. Incidents are frequent in which someone puts it in the application namespace and asks why it does not attach.

A binding holds only when both conditions are met. The Gateway name must be in the VirtualService's gateways, and the hosts must overlap the Gateway's server hosts. To apply the same rule to internal mesh traffic too, add the reserved word mesh to gateways.

A ServiceEntry is the resource in the opposite direction, that is, the one that brings services outside the mesh inside. Once you register one, you can attach timeouts, retries, mirroring, and metrics to an external API as well. For resolution it is enough to distinguish STATIC (use the IPs written in endpoints), DNS (name lookup), and NONE (use the original request address). If you turn on outboundTrafficPolicy: REGISTRY_ONLY mesh-wide, external hosts that are not in a ServiceEntry are blocked.

The Sidecar resource is different in nature. By default, every sidecar receives the configuration of every service in the mesh. When there are thousands of services, this configuration alone inflates Envoy memory to hundreds of MiB. If you narrow egress.hosts with a Sidecar, only the scope that workload needs to know comes down. The format is 네임스페이스/호스트 (namespace and host), and ./* is its own namespace and istio-system/* is the control plane namespace.

You should also know the direction going forward. For new ingress, writing it with the Kubernetes Gateway API (Gateway + HTTPRoute) is recommended, and the ambient mode waypoint proxy itself is declared as a Gateway resource of the Gateway API. Istio's own Gateway/VirtualService continue to be supported, but new standard features land first on the Gateway API side.

What it looks like in the field

In the author's homelab, bringing up Cilium's Gateway API implementation got stuck once. When the Gateway API CRDs were installed at v1.2, the controller refused to start, and the cause was that tlsroutes and referencegrants were not yet v1. It attached only after the CRDs were raised to v1.6.1.

The lesson applies equally to Istio. The Gateway API is a separate project whose CRDs must exist on the cluster first, and the implementation (Istio, Cilium) and the CRD version must match. This is the answer to the question "I ran istioctl install, so why can't I create an HTTPRoute?" The hubble-relay and hubble-ui staying Pending in the same homelab was a problem of a similar kind. They are Deployments rather than DaemonSets, so they did not tolerate the control plane taint, and it was resolved right away when the workers joined. Gateway Deployments also often fail to be scheduled for the same reason.

What you will do in the next lab

After actually creating a backend workload and a kubernetes.io/tls Secret, you put a SIMPLE server and a PASSTHROUGH server in one Gateway, bind it with a VirtualService, register an external payment API as a ServiceEntry, and then narrow that workload's field of view to three entries with a Sidecar.