metalstack cloud

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-system

Now 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-system

Deploying 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
EOF

with the important parts being:

  • annotations inform gardener that we need a wildcard certificate
  • gatewayClassName as we need this later to reference
  • the listener named http that matches all hostnames and listens on port 80
  • the listener named https that matches all hostnames and listens on port 443, and additionally terminates tls traffic using a letsencrypt certificate
  • by default only selected namespaces are allowed to use a gateway, with allowedRoutes.namespaces.from=All we 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.0

Now 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: 301

This deploys 2 HTTPRoutes:

  • echo-http referencing the http listener in the gateway publishing a permanent redirect to https
  • echo referencing the https listener in the gateway publishing the actual route to the application service (named my-echo-server in our example)

Verifying the setup

Now we can test our http HTTPRoute using curl:

curl -s -v -L http://echo.demo.f8e67080ba.k8s.metalstackcloud.io

The verbose curl output confirms a few things:

  1. The automated DNS record creation works as expected

    * Host echo.demo.f8e67080ba.k8s.metalstackcloud.io:80 was resolved
  2. Our 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/
  3. 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' | jq

Which 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 Gateway resource using DNS and TLS managed by gardener
  • expose an application using a HTTPRoute (with automated http -> https redirect)

Good job!

You've completed this developer guide. We have many more, so feel free to explore our other guides! Should you have any questions about our products or need help with metalstack.cloud, please reach out to us anytime.