Open Two Doors, Post Two Guards
Goal
On a real Istio mesh, open ingress with the two approaches, the Istio Gateway and the Gateway API, and confirm from responses and Envoy configuration where TLS termination, weighted distribution, and gateway authorization policies actually apply.
Why it matters
Ingress is the mesh's first gate, so a mistake here is visible right away from outside. But now that Istio and the Gateway API are used together, it is easy to end up with two doors for the same service, and the two doors are different Pods, so the certificates and the policies are separate too. The difference is not visible merely from a manifest being 'applied', so you confirm with real requests and the configuration the gateway Envoy received.
Steps
- Put up web-v1 and web-v2 in the namespace
mallwithkubectl apply -f /opt/fixtures/istlab/ingress-app.yamland wait until both Deployments are ready. Then write two lines into/root/istlab-ingress/01-gateway.txt: the ingress gateway'singress_cluster_ip=(the ClusterIP of the Serviceistio-system/istio-ingressgateway) andingress_pod=(that gateway Pod's name). - Write two resources into
/root/istlab-ingress/gateway.yamland apply them — the IstioGatewaymall-gwin the namespacemall(selectoristio: ingressgateway, port 80 HTTP, hostmall.example.com) and theVirtualServicemall-ingress(hostmall.example.com, gatewaymall-gw, destinationweb-v1.mall.svc.cluster.localport 80). After applying, if you call the gateway's ClusterIP withHost: mall.example.com,v1must come back. - With
openssl, make a self-signed certificate and key withCN=mall.example.comand SANDNS:mall.example.comas/root/istlab-ingress/tls/mall.crtand/root/istlab-ingress/tls/mall.key, and put them intoistio-systemas the TLS Secretmall-cred. Then add a port 443 HTTPS server (hostmall.example.com,tls.mode: SIMPLE,credentialName: mall-cred) to the Gateway in/root/istlab-ingress/gateway.yamland apply again.curl --cacert /root/istlab-ingress/tls/mall.crt --resolve mall.example.com:443:<ClusterIP> https://mall.example.com/must returnv1. - Write two resources into
/root/istlab-ingress/gwapi.yamland apply them — the ConfigMapmall-api-optionsin the namespacemall(spec: {type: ClusterIP}indata.service) and the Gateway APIGatewaymall-api(gatewayClassName: istio, that ConfigMap throughinfrastructure.parametersRef, a listenerhttpon port 80 HTTP, host nameapi.mall.example.com, allowing only routes from the same namespace). Wait until the Gateway becomesProgrammed. - Write the
HTTPRouteapi-splitinto/root/istlab-ingress/httproute.yamland apply it — the parent is the Gatewaymall-api, the host name isapi.mall.example.com, and there are two rules: if the header isx-canary: yes, go toweb-v2(port 80), and otherwise split by weights ofweb-v180 andweb-v220. If you send a request withx-canary: yesto the ClusterIP of themall-api-istioService,v2must always come back. - Write four lines into
/root/istlab-ingress/06-compare.txt— the namespace of the gateway Pod the Istio Gateway pickedistio_gateway_ns=, the namespace of the gateway Pod the Gateway API madegwapi_gateway_ns=, that Deployment's namegwapi_deployment=, and that Service's typegwapi_service_type=. - Write the
AuthorizationPolicydeny-admininistio-systeminto/root/istlab-ingress/deny-admin.yamland apply it — selectoristio: ingressgateway,action: DENY, paths/adminand/admin/*. After applying, write two lines into/root/istlab-ingress/07-scope.txt:istio_gateway_admin=(the status code ofmall.example.com/admin) andgwapi_admin=(the status code of sendingapi.mall.example.com/adminto the Gateway API gateway). - Write five lines into
/root/istlab-ingress/08-report.md—tls_fingerprint_match=(yes if the SHA-256 fingerprint of the certificate the gateway presents on 443 equals/root/istlab-ingress/tls/mall.crt),gwapi_namespace=,weights=(the v1/v2 weights, for example50/50),admin_via_istio_gateway=, andadmin_via_gateway_api=— and write what you learned below that in at least four lines.
Notes
- This VM takes 2–4 minutes to prepare.
kubectlandistioctl(1.31.0) can be used directly in the login shell. - Call the gateway by its ClusterIP instead of the LoadBalancer address —
kubectl -n istio-system get svc istio-ingressgateway -o jsonpath='{.spec.clusterIP}'. The VM's shell can reach cluster Service addresses. kubectl get gatewayis ambiguous about whether it means Istio or the Gateway API. Write the group too, as ingateways.networking.istio.ioorgateways.gateway.networking.k8s.io.- Right after you change the configuration, it takes a few seconds to spread to the gateway. Do not judge in one try; repeat a few times.
- Common mistake — creating the TLS Secret in the
mallnamespace. It must be inistio-system, where the gateway Pod is.
Put two copies of the app in the mesh and find the gatekeeper
Put up web-v1 and web-v2 in the namespace mall with kubectl apply -f /opt/fixtures/istlab/ingress-app.yaml and wait until both Deployments are ready. Then write two lines into /root/istlab-ingress/01-gateway.txt: the ingress gateway's ingress_cluster_ip= (the ClusterIP of the Service istio-system/istio-ingressgateway) and ingress_pod= (that gateway Pod's name).
The default profile sets up istio-ingressgateway in advance along with istiod. This gateway is a standalone Envoy, not a sidecar, and right now it receives no configuration, so it gives a 404 to any request. The Service type is LoadBalancer so k3s attaches a node address, but in the lab you call it by the steady ClusterIP. Pick the Pod name with -l istio=ingressgateway.
Open the door with an Istio Gateway and a VirtualService
Write two resources into /root/istlab-ingress/gateway.yaml and apply them — the Istio Gateway mall-gw in the namespace mall (selector istio: ingressgateway, port 80 HTTP, host mall.example.com) and the VirtualService mall-ingress (host mall.example.com, gateway mall-gw, destination web-v1.mall.svc.cluster.local port 80). After applying, if you call the gateway's ClusterIP with Host: mall.example.com, v1 must come back.
Istio's Gateway only picks the already running gateway Pod with a selector and tells it 'receive on this port and host'; it does not know where to send. That is decided by a VirtualService that writes that Gateway name in gateways. If only one of the two exists, it is a 404. It takes a few seconds to take effect, so repeat until the response changes. Kubernetes has two resources named gateway (Istio and Gateway API), so when querying it is safer to write the group too, as in kubectl get gateways.networking.istio.io.
End TLS at the gateway
With openssl, make a self-signed certificate and key with CN=mall.example.com and SAN DNS:mall.example.com as /root/istlab-ingress/tls/mall.crt and /root/istlab-ingress/tls/mall.key, and put them into istio-system as the TLS Secret mall-cred. Then add a port 443 HTTPS server (host mall.example.com, tls.mode: SIMPLE, credentialName: mall-cred) to the Gateway in /root/istlab-ingress/gateway.yaml and apply again. curl --cacert /root/istlab-ingress/tls/mall.crt --resolve mall.example.com:443:<ClusterIP> https://mall.example.com/ must return v1.
The gateway does not mount certificate files. istiod pushes down the Secret that credentialName points to to the gateway Envoy through SDS, and if you change the Secret, it uses the new certificate without restarting the gateway. That is why you put the Secret in the same namespace as the gateway Pod (istio-system). curl --resolve binds the name to an address without DNS, so that you check SNI and certificate validation together. You look at what the gateway received with istioctl proxy-config secret -n istio-system deploy/istio-ingressgateway.
The Gateway API stands up a new gateway
Write two resources into /root/istlab-ingress/gwapi.yaml and apply them — the ConfigMap mall-api-options in the namespace mall (spec: {type: ClusterIP} in data.service) and the Gateway API Gateway mall-api (gatewayClassName: istio, that ConfigMap through infrastructure.parametersRef, a listener http on port 80 HTTP, host name api.mall.example.com, allowing only routes from the same namespace). Wait until the Gateway becomes Programmed.
Unlike Istio's Gateway, the Gateway API's Gateway creates the gateway Deployment and Service by itself (the name is <Gateway 이름>-istio, where the placeholder is the Gateway name). The default Service type is LoadBalancer, but this VM's k3s has already given port 80 to the ingress gateway, so the new LoadBalancer cannot get an address and stays at <pending> — then the Gateway does not become Programmed (measured). As the official docs say, point to a ConfigMap with infrastructure.parametersRef and change the Service type. The status is in the conditions of kubectl -n mall get gateways.gateway.networking.k8s.io mall-api -o yaml.
Split by header and weight with an HTTPRoute
Write the HTTPRoute api-split into /root/istlab-ingress/httproute.yaml and apply it — the parent is the Gateway mall-api, the host name is api.mall.example.com, and there are two rules: if the header is x-canary: yes, go to web-v2 (port 80), and otherwise split by weights of web-v1 80 and web-v2 20. If you send a request with x-canary: yes to the ClusterIP of the mall-api-istio Service, v2 must always come back.
An HTTPRoute states for itself which Gateway it attaches to with parentRefs (the direction is the same as what an Istio VirtualService states with gateways). The more specific rule (the header condition) wins. Checking weights with a few requests is shaky, so confirm in the route table the gateway Envoy actually received — weightedClusters in istioctl proxy-config routes deploy/mall-api-istio -n mall -o json.
Where do the two gateways stand
Write four lines into /root/istlab-ingress/06-compare.txt — the namespace of the gateway Pod the Istio Gateway picked istio_gateway_ns=, the namespace of the gateway Pod the Gateway API made gwapi_gateway_ns=, that Deployment's name gwapi_deployment=, and that Service's type gwapi_service_type=.
The Istio Gateway picks and uses an existing gateway, and the Gateway API stands up a gateway in the namespace where the Gateway is. So each team can keep a gateway in its own namespace and scale it separately. In exchange, the resources increase as the gateways increase. What it made carries the label gateway.networking.k8s.io/gateway-name=mall-api.
Block /admin in front of the door — and the door that is not blocked
Write the AuthorizationPolicy deny-admin in istio-system into /root/istlab-ingress/deny-admin.yaml and apply it — selector istio: ingressgateway, action: DENY, paths /admin and /admin/*. After applying, write two lines into /root/istlab-ingress/07-scope.txt: istio_gateway_admin= (the status code of mall.example.com/admin) and gwapi_admin= (the status code of sending api.mall.example.com/admin to the Gateway API gateway).
A gateway is also an Envoy workload, so you can attach an authorization policy to it with a selector, and if you block at the gateway, the request does not even enter the mesh. But what the selector picked is only the istio-ingressgateway Pod. The gateway the Gateway API stood up is a different Pod, so this policy is not applied — it means that the moment you opened the second door, the first door's guard did not guard that door. Leave that fact down as a number.
Write down what to check when there are two doors
Write five lines into /root/istlab-ingress/08-report.md — tls_fingerprint_match= (yes if the SHA-256 fingerprint of the certificate the gateway presents on 443 equals /root/istlab-ingress/tls/mall.crt), gwapi_namespace=, weights= (the v1/v2 weights, for example 50/50), admin_via_istio_gateway=, and admin_via_gateway_api= — and write what you learned below that in at least four lines.
For the fingerprint, you can look side by side at the certificate from openssl s_client -connect <ClusterIP>:443 -servername mall.example.com and the file with openssl x509 -noout -fingerprint -sha256. Copy the rest from the records of steps 6 and 7.