Skip to main content
By the end of this page something you run answers on a real hostname, over HTTPS, with a certificate that was issued rather than self-signed — and you will know which of the two ways to publish costs you an address and which does not. This page starts where Run a VM and Add a disk to a VM ended: a workload running in your project.

What this changes on your cluster

Depends entirely on which path you take, and that is the decision this page is about: The Gateway path shares one address across every hostname published through it. The LoadBalancer path gives each publication its own. Neither is wrong — but on an estate where addresses are a finite, planned resource, the difference compounds.

Before you start

  • Certificates, from onboarding — cert-manager plus a ClusterIssuer. Without one the Gateway path publishes without TLS, which is the outcome it exists to avoid.
  • A domain on the supervisor. Hostnames derive from it; this is the field set back on Register a supervisor.
  • DNS Publishing, if you want the A record created for you. Without it the route works and nothing resolves — publish the name yourself, or install external-dns.
  • Permission vm:Expose for a VM port, or gateway:Create for a platform route.

The shape of it

Pick the path → name the hostname → publish → prove it from outside. The last step is the one that catches a missing DNS record, which is otherwise invisible from inside the cluster.

Step 1 — Choose LoadBalancer or Gateway

Take the Gateway when the thing speaks HTTP. You get TLS terminated by cert-manager, an HTTP→HTTPS redirect, and no address consumed. This is the same mechanism the platform uses to publish Grafana and Prometheus. Take the LoadBalancer when it does not. Gateway exposure is HTTP/TCP-shaped only; a non-TCP protocol is refused outright, and raw TCP — SSH, RDP — is explicitly not implemented: “raw TCP via TCPRoute/TLSRoute is not implemented yet”. Those need the LoadBalancer path and an address of their own.
The VM detail page → Networking. Each port the guest listens on has Expose, which publishes it on the VM’s LoadBalancer, and Gateway, which opens a form naming the Gateway and the hostname it will use.
The Expose port 80 via Gateway dialog with the Gateway set to the project's own envoy, the hostname team-k-web.prod.celum.example.com, an Advanced section, and the buttons Cancel and Expose via Gateway

Gateway on a VM port: the Gateway it lands on and the hostname, filled in for you

Step 2 — Know which Gateway you land on

You may name one with gatewayRef as namespace/name. Leave it out and Celum resolves, in order:
  1. the project’s dedicated Gateway, <project>-envoy, when the project has one (dedicatedGateway). It replaces tenant-gw, and every later publication in the project lands on it;
  2. otherwise the project’s own tenant-gw, created if it does not exist yet;
  3. failing that, the platform’s shared-gateway.
Each project gateway gets its own LoadBalancer address. So the first publication in a project does consume one — and every publication after it, in that project, is free. The saving is per project, not per route.
The backing Service is deliberately separate: <vm>-gw is a ClusterIP selecting vm.kubevirt.io/name=<vm>, distinct from <vm>-lb. A VM can therefore be published both ways at once without the two interfering.

Step 3 — Publish

For a VM port, send the port, the hostname as an FQDN, and optionally a clusterIssuer — it defaults to letsencrypt-prod. For a Service that is not a VM, the platform route endpoint takes the same shape one level up: POST /api/supervisors/{name}/networking/routes with hostname, serviceName, servicePort, a gatewayRef of namespace/name, and a clusterIssuer. Creating a Gateway of your own, rather than using one that exists, is POST .../networking/gateways: gatewayClassName defaults to cilium on a supervisor and is envoy-gateway inside a guest cluster — each surface accepts only its own class. requestedIP pins a specific address from the pool and is cilium-only; datapath (cluster or daemonset) is envoy-gateway-only. Result: the route exists and the Gateway lists the hostname. That is not yet proof that anything answers.

Verify — from outside, not from inside

Three things have to be true, and they fail independently:
  1. The route attached. The Gateway reports the hostname on a listener.
  2. The certificate was issued. A Certificate that stays Pending usually means the ClusterIssuer cannot solve DNS-01 — which is a certificates problem, not a routing one.
  3. The name resolves. With DNS Publishing installed this happens on its own; without it, nothing does, and the route looks perfectly healthy while being unreachable.
Then probe it from outside the cluster, on the protocol you expect:
Probe both protocols if you care about both. An estate audit found the same app answering on HTTP on one surface and HTTPS on another — a bare LoadBalancer published :80 with no TLS, while a Gateway published :443 with no :80 listener. Same application, opposite answers to “does https work here?”, and neither surface showed anything wrong from the inside. A shared Gateway serving both ports is what removed the ambiguity.

If you need to undo it

Both paths are reversible and neither destroys anything: unexpose_vm_port removes the LoadBalancer Service, unexpose_vm_gateway removes the HTTPRoute and its ClusterIP. Routes created at the platform level come off with DELETE .../networking/routes/{route} (gateway:Delete). Deleting a Gateway is a wider action than deleting a route — every route still pointing at it stops working, and the shared address goes with it.
Removing a route does not necessarily remove its DNS record. Where external-dns manages the zone with policy sync, it reconciles the deletion. With upsert-only, or where it does not manage the zone, the name keeps resolving to an address that no longer answers.

What commonly goes wrong

Next

Gateways & Routes

Listeners, TLS, datapath modes, and publishing tenant APIs.

VM networking

The VM-side view, including what the guest is actually listening on.

Pools & IPAM

Where LoadBalancer addresses come from, and how full the pool is.

Security overview

Seeing who is actually reaching what you just published.