Service exposure using the Gateway API
The Gateway API is a modern and extensible way to expose services inside a Kubernetes Cluster. In principle it achieves the same as a Ingress Controller, but its modular and thus more customizable. In this guide we’ll take a look at using Istio to expose a HTTP Service to the internet. Note that there is an extensive list of solutions offering Gateway API support, this guide choose Istio because its fully compliant with the Gateway API.
Prerequisites
Before starting the installation, ensure you meet the following prerequisites:
- Kubernetes 1.26+: For this guide, we are using metalstack.cloud with Kubernetes version 1.32.11. To learn how you can deploy a cluster programmatically check out this article.
- Helm 3.x: You’ll need Helm to manage the chart installation.
Installing Istio
The first step is to install the Gateway API CRDs. This can be done by applying the manifest located in the official kubernetes-sigs repository:
kubectl kustomize "github.com/kubernetes-sigs/gateway-api/config/crd?ref=v1.4.0" | kubectl apply -f -Next up we have to install the Istio CRDs, which are packaged conveniently in a base helm chart.
# ensure the istio-system namespace is present
kubectl create ns istio-system
# add the istio-official helmchart repo
helm repo add istio-official https://istio-release.storage.googleapis.com/charts
# install the base chart to the istio-system namespace
helm install base istio-official/base --version 1.28.2 --namespace istio-systemNow we can deploy the core component of Istio, istiod:
# install the istiod chart to the istio-system namespace
helm install istiod istio-official/istiod --version 1.28.2 --namespace istio-systemDeploying our first Gateway
After the groundwork of CRDs and istiod is deployed we can configure our Gateway CR. In this example we’ll deploy a Gateway in the gateway.networking.k8s.io/v1 API not to be confused with the Istio Gateway in the networking.istio.io API.
In this example we’ll be utilizing the cluster domain provided by metalstack.cloud. To retrieve it, execute the following command:
kubectl config view --minify --output jsonpath="{.clusters[*].cluster.server}"The output should resemble:
https://api.demo.0123456789.k8s.metalstackcloud.io We’re interested in the part following https://api.:
export CLUSTER_DOMAIN=$(kubectl config view --minify --output jsonpath='{.clusters[*].cluster.server}' | cut -d'.' -f2-)An example manifest could look like this:
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: gateway
namespace: istio-system
annotations:
dns.gardener.cloud/dnsnames: "*.demo.${CLUSTER_DOMAIN}"
dns.gardener.cloud/class: garden
cert.gardener.cloud/purpose: managed
spec:
gatewayClassName: istio
listeners:
- name: http
hostname: "*.demo.${CLUSTER_DOMAIN}" # this is used by dns-controller-manager to extract DNS names
port: 80
protocol: HTTP
allowedRoutes:
namespaces:
from: All
- name: https
hostname: "*.demo.${CLUSTER_DOMAIN}" # this is used by dns-controller-manager to extract DNS names
port: 443
protocol: HTTPS
allowedRoutes:
namespaces:
from: All
tls:
mode: Terminate
certificateRefs:
- name: tls-wildcard
EOFwith the important parts being:
annotationsinform gardener that we need a wildcard certificategatewayClassNameas we need this later to reference- the listener named
httpthat matches all hostnames and listens on port80 - the listener named
httpsthat matches all hostnames and listens on port443, and additionally terminatestlstraffic using a letsencrypt certificate - by default only selected namespaces are allowed to use a gateway, with
allowedRoutes.namespaces.from=Allwe open up the gateway for cluster wide use
Deploying an example application and HTTPRoute
As an example application for this case we are going to use a simple REST echo server.
To deploy it with helm:
# Add the app helm repo
helm repo add ealenn https://ealenn.github.io/charts
# Get helm repo updates
helm repo update
# Install the echo server chart to the default namespace
helm upgrade --install my-echo-server ealenn/echo-server --version 0.5.0Now we can apply a HTTPRoute for our application:
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
annotations:
dns.gardener.cloud/class: garden
dns.gardener.cloud/dnsnames: echo.demo.f8e67080ba.k8s.metalstackcloud.io
dns.gardener.cloud-tes/ttl: "180"
name: echo
spec:
hostnames:
- echo.demo.f8e67080ba.k8s.metalstackcloud.io
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: gateway
namespace: istio-system
sectionName: https
rules:
- backendRefs:
- group: ""
kind: Service
name: my-echo-server
port: 80
weight: 1
matches:
- path:
type: PathPrefix
value: /
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
annotations:
dns.gardener.cloud/class: garden
dns.gardener.cloud/dnsnames: echo.demo.f8e67080ba.k8s.metalstackcloud.io
dns.gardener.cloud-tes/ttl: "180"
name: echo-http
spec:
hostnames:
- echo.demo.f8e67080ba.k8s.metalstackcloud.io
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: gateway
namespace: istio-system
sectionName: http
rules:
- filters:
- type: RequestRedirect
requestRedirect:
scheme: https
statusCode: 301This deploys 2 HTTPRoutes:
echo-httpreferencing thehttplistener in the gateway publishing a permanent redirect to httpsechoreferencing thehttpslistener in the gateway publishing the actual route to the application service (namedmy-echo-serverin our example)
Verifying the setup
Now we can test our http HTTPRoute using curl:
curl -s -v -L http://echo.demo.f8e67080ba.k8s.metalstackcloud.ioThe verbose curl output confirms a few things:
The automated DNS record creation works as expected
* Host echo.demo.f8e67080ba.k8s.metalstackcloud.io:80 was resolvedOur http to https redirect is working correctly
* Request completely sent off < HTTP/1.1 301 Moved Permanently < location: https://echo.demo.f8e67080ba.k8s.metalstackcloud.io/The automated TLS cert generation via letsencrypt is working correctly
* SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256 / [blank] / UNDEF * ALPN: server accepted h2 * Server certificate: * subject: CN=*.demo.f8e67080ba.k8s.metalstackcloud.io * start date: Jan 9 13:24:45 2026 GMT * expire date: Apr 9 13:24:44 2026 GMT * subjectAltName: host "echo.demo.f8e67080ba.k8s.metalstackcloud.io" matched cert's "*.demo.f8e67080ba.k8s.metalstackcloud.io" * issuer: C=US; O=Let's Encrypt; CN=R12 * SSL certificate verify ok.
Now we can check again without the verbose flag and focus on the answer we get back from the echo server:
curl -s 'https://echo.demo.f8e67080ba.k8s.metalstackcloud.io/param?query=metalstackclouddemo&foo=bar' | jqWhich shows us, among much more info, our query parameters:
[...]
"request": {
"params": {
"0": "/param"
},
"query": {
"query": "metalstackclouddemo",
"foo": "bar"
},Therefore confirming that our HTTPRoute is working correctly.
Conclusion
In this example we learned how to:
- install istio in its leanest form, it has a lot of more features which we are not covering here
- configure and deploy a
Gatewayresource using DNS and TLS managed by gardener - expose an application using a
HTTPRoute(with automated http -> https redirect)