Skip to content

Expose a service with the Gateway API

The Gateway API is the newer, more expressive successor to Ingress. On skube the ingress bundle ships the Gateway API CRDs, a provider-managed traefik GatewayClass, and cert-manager wired for Gateway certificates. You write your own Gateway and HTTPRoute; the cluster is single-tenant, so you own them outright.

If you just need to publish one service, Ingress is simpler. Reach for the Gateway API when you want route-level control: header and method matching, traffic splitting, or many routes sharing one entry point.

We start over plain HTTP to confirm routing works, then add a domain and HTTPS by overwriting the same Gateway.

Terminal window
cat <<'EOF' | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-gateway
spec:
replicas: 1
selector:
matchLabels:
app: hello-gateway
template:
metadata:
labels:
app: hello-gateway
spec:
containers:
- name: hello-gateway
image: nginx
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: hello-gateway
spec:
selector:
app: hello-gateway
ports:
- port: 80
targetPort: 80
EOF

Create an HTTP-only Gateway with no hostname and an HTTPRoute with no hostnames. With neither set, the listener and route match any request, so Traefik serves it to anyone hitting the node:

Terminal window
cat <<'EOF' | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: hello-gateway
spec:
gatewayClassName: traefik
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: Same
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: hello-gateway
spec:
parentRefs:
- name: hello-gateway
rules:
- backendRefs:
- name: hello-gateway
port: 80
EOF

Reach it directly at your node IP (the same IP as your API server, https://<your-node-ip>:6443):

Terminal window
curl http://<your-node-ip>

You should get the nginx welcome page.

To serve real traffic over HTTPS you need a hostname that resolves to the cluster. Let’s Encrypt validates over an HTTP-01 challenge, so DNS must resolve before you apply.

In your DNS provider, create an A record pointing your hostname at your cluster’s node IP:

gateway.example.com. A <your-node-ip>

Confirm it resolves before continuing:

Terminal window
dig +short gateway.example.com

Now overwrite the Gateway with your real hostname, the cert-manager.io/cluster-issuer annotation, and an HTTPS listener whose certificateRefs point at the hello-gateway-tls secret. The HTTP listener stays for the ACME challenge and plain-HTTP traffic; cert-manager fills the secret.

Terminal window
cat <<'EOF' | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: hello-gateway
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
gatewayClassName: traefik
listeners:
- name: http
protocol: HTTP
port: 80
hostname: gateway.example.com
allowedRoutes:
namespaces:
from: Same
- name: https
protocol: HTTPS
port: 443
hostname: gateway.example.com
allowedRoutes:
namespaces:
from: Same
tls:
mode: Terminate
certificateRefs:
- kind: Secret
name: hello-gateway-tls
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: hello-gateway
spec:
parentRefs:
- name: hello-gateway
hostnames:
- gateway.example.com
rules:
- backendRefs:
- name: hello-gateway
port: 80
EOF

The certificate takes a minute or two on first issuance. Watch it become ready:

Terminal window
kubectl get certificate hello-gateway-tls -w

Once READY is True, your app is live over HTTPS:

Terminal window
curl https://gateway.example.com

If issuance stalls, check the Gateway and certificate status:

Terminal window
kubectl describe gateway hello-gateway
kubectl describe certificate hello-gateway-tls

The usual cause is DNS that doesn’t yet resolve to your node IP.

Terminal window
kubectl delete httproute hello-gateway
kubectl delete gateway hello-gateway
kubectl delete service hello-gateway
kubectl delete deployment hello-gateway
kubectl delete secret hello-gateway-tls --ignore-not-found