Hosted Control Plane and tenant networking¶
Challenge: running a hosted cluster in a different tenant network segment or VLAN without wide-open access from the tenant segment to the management segment.
Hub cluster must not route into tenant networks
The hub cluster must not have arbitrary Layer-3 addressing or routing into the tenant network segment. The hub may only attach hosted-cluster workloads—for example, KubeVirt VMs on a UDN localnet—to that segment.
Do not use MetalLB on the hub to expose services into a tenant network. That pattern typically requires:
- Tenant-network IP addressing on hub bare-metal nodes
- Enabling IP forwarding globally
- Enabling
routingViaHost
Together, those changes let any workload on the hub reach tenant networks—not only the hosted cluster you intend to isolate.
This guide uses external load balancers in the tenant segment and a dedicated ingress controller shard on the hub instead.
LoadBalancer Services in hosted clusters get IPs from the hub
There is currently no supported way to disable or constrain this. If the hub cluster supports Kubernetes Services of type LoadBalancer (e.g. via MetalLB, a cloud provider CCM, or any other implementation), any such Service created inside the hosted cluster will be fulfilled by the hub's load-balancer implementation. This means workloads in the tenant network can inadvertently obtain IP addresses from the hub's network.
Tracking: RFE-7742
An hosted cluster can devide into two parts: control plane and data plan aka worker nodes. For there parts there different technics to place it into a tenant network:
Exposing hosted control plane into tenant network¶
... it is fairly hard. The following components must be reachable from workers and clients in/from tenant network and beyond:
- API Server
- OAuth
- Konnectivity
- Ignition
Here is a summary of common publishing options for these components:
| Component/Service | Exposing strategy (servicePublishingStrategy) |
Kubernetes Service type LoadBalancer |
Route (OpenShift router) |
|---|---|---|---|
| API Server | LoadBalancer service) |
✅ | ❌ |
| OAuth | ❌ | ✅ | |
| Konnectivity | LoadBalancer service) |
✅ | ✅ |
| Ignition | ❌ | ✅ |
For this proof of concept, endpoints are exposed as follows:
- API Server:
LoadBalancer(fronted by externalapi-lbin the tenant segment; see below) - OAuth, Konnectivity, Ignition:
Routevia a dedicated ingress controller shard on the hub, fronted by externalingress-shared-lbwith VIPs/DNS in the tenant segment
Exposing components via a dedicated router shard¶
Use a dedicated OpenShift Ingress Controller shard on the hub so only the hosted-cluster control-plane Routes are served by that shard. Tenant clients resolve OAuth, Konnectivity, and Ignition hostnames to ingress-shared-lb, which forwards to the shard’s NodePorts on the management network.
Place an external load balancer in front of that shard (for example F5 BIG-IP or NetScaler) that can reach the hub’s management network and present stable tenant-facing VIPs or addresses.
Exposing hosted worker nodes into netant network¶
Worker nodes (VM's) of the hosted cluster are straightforward: attach them to the tenant network segment (DHCP or equivalent addressing is required).
Proof of concept environment overview¶
Router between Mgmt and Tenant-A¶
VyOS acts as router and firewall between the management and the Tenant-A network. Restrict lateral traffic between the two segments (no full mesh); allow only what you need (for example DNS to resolvers, default route or NAT for internet egress). Hosted-cluster control-plane traffic from tenant nodes should flow to the external load balancer VIPs in the tenant segment (not directly into arbitrary management subnets).
VyOS config commands
Boot and install VyOS from ISO: https://docs.vyos.io/en/rolling/installation/install.html#permanent-installation
Don't forget to commit and save changes. With compare you can see your chages.
Configuration active after reboot is stored /opt/vyatta/etc/config/config.boot (recommened) or /opt/vyatta/etc/config.boot.default deppend on your settings during iso installation.
FYI: show dhcp server leases shows you all dhcpd leases
Tunneling the network via sshuttle
Deployment sequence (reference)¶
Three external load balancers appear in this write-up; keep their roles distinct:
| Name | Role |
|---|---|
ingress-shared-lb |
Tenant-facing VIPs for OAuth, Konnectivity, Ignition Routes on the hub ingress shard |
api-lb |
Tenant-facing VIP for the hosted cluster API (APIServer publishing) |
ingress-lb |
Tenant-facing VIP for hosted cluster application Routes (*.apps…) |
Suggested order:
- Hub ingress shard +
ingress-shared-lb+ DNS for the three control-plane hostnames: OAuth, Konnectivity, and Ignition - Apply
HostedClusterandNodePool. - Deploy external load balancer for the Hosted-Cluster API
api-lb+ API DNS. Based on the NodePorts for the api kubernetes Service, located in hub cluster - Deploy external load balancer for the Hosted-Cluster Ingress
ingress-lb+ wildcard apps DNS. Based on the NodePorts of the ingress kubernetes service, located in hosted cluster.
Hub ingress shard + ingress-shared-lb¶
- 2.3.4. Ingress sharding in OpenShift Container Platform
- 3.1.3.8.1. Example load balancer configuration for user-provisioned clusters
Ingress Controller
The ingress shard load balancer is an RHEL 9 host running HAProxy (external load balancer ingress-shared-lb).
- Install HAProxy:
dnf install haproxy - Configure SELinux:
setsebool -P haproxy_connect_any 1 - Apply the example
haproxyconfiguration (update ports to match your NodePort service) - Enable and start HAProxy:
systemctl enable --now haproxy
HAProxy config
Add DNS records
Apply PullSecret, SSH-Key¶
Apply HostedCluster and NodePool¶
appsDomain: resolve names underapps.tenant-a.coe.muc.redhat.comtoingress-lb(hosted cluster ingress), not the hub shard.- API server
loadBalancer.hostname: resolve toapi-lb, which forwards to theAPIServerpublishing target on the hub. - OAuth
route.hostname: resolve toingress-shared-lb(hub dedicated shard). - Konnectivity
route.hostname: resolve toingress-shared-lb. - Ignition
route.hostname: resolve toingress-shared-lb. - Switch from Kubernetes Type LoadBalancer to just
HostNetworkasendpointPublishingStrategy
- Attach NodePool VMs to the tenant segment using a user-defined network (UDN)
localnetattachment (cudn-localnet1-2003in this lab).
ClusterUserDefinedNetwork for cudn-localnet1-2003
Deploy external load balancer for the Hosted-Cluster API (api-lb)¶
NodePort ports are allocated randomly
The HAProxy backends point to NodePort high ports that are assigned dynamically. If the Service is recreated, ports may change and HAProxy must be reconfigured. See NodePort chicken-and-egg problem for a discussion and potential solutions.
Use an RHEL 9 virtual machine with HAProxy.
- Install HAProxy:
dnf install haproxy - Configure SELinux:
setsebool -P haproxy_connect_any 1 - Apply the example
haproxyconfiguration (update ports to match your environment) - Enable and start HAProxy:
systemctl enable --now haproxy
HAProxy config
Add DNS record:
Deploy external load balancer for Hosted-Cluster ingress (ingress-lb)¶
Use an RHEL 9 virtual machine with HAProxy.
- Install HAProxy:
dnf install haproxy - Configure SELinux:
setsebool -P haproxy_connect_any 1 - Apply the example
haproxyconfiguration (update ports to match your environment) - Enable and start HAProxy:
systemctl enable --now haproxy
HAProxy config
Add DNS record:
LoadBalancer Services in hosted clusters get IPs from the hub¶
When the hub cluster supports Kubernetes Services of type LoadBalancer (e.g. via MetalLB, a cloud provider CCM, or any other implementation), any such Service created inside the hosted cluster is fulfilled by the hub cluster's load-balancer implementation. The hosted cluster's control plane runs on the hub and inherits its configuration.
This means a tenant workload can unintentionally allocate an IP address from the hub's infrastructure network simply by creating a LoadBalancer Service.
There is currently no supported way to disable this behaviour. Tracking: RFE-7742
Reproducing the problem¶
Deployment for a Kubernetes Service type LoadBalancer
The Service will receive an EXTERNAL-IP from the hub's cloud provider—not from the tenant network.
NodePort chicken-and-egg problem¶
The external HAProxy load balancers (api-lb, ingress-shared-lb, ingress-lb) in this setup forward traffic to Kubernetes Services of type NodePort. The problem: NodePort allocates a random high port (default range 30000–32767) that is only known after the Service is created. Every time the Service is recreated or the port changes, the HAProxy configuration must be updated manually.
This creates a chicken-and-egg situation especially for the API load balancer: you need the NodePort to configure HAProxy, but the hosted cluster needs a working API endpoint to become healthy.
Why an external LoadBalancer controller would solve this¶
An external load balancer that integrates with Kubernetes (i.e. watches Service objects and configures itself automatically) eliminates the manual NodePort tracking entirely. Such a controller:
- Reacts to Service creation/updates and provisions the correct backend configuration automatically
- Can expose Services into separate network segments (tenant networks) with its own VIP pool
- Removes the need for static HAProxy configs that break when ports change
Potential solutions¶
| Product | Notes |
|---|---|
| F5 BIG-IP (Container Ingress Services - CIS) | Watches Kubernetes Services, provisions VIPs and pool members on BIG-IP |
| NetScaler (formerly Citrix ADC) | Kubernetes integration via Citrix Ingress Controller |
| A10 Networks (Thunder ADC) | Thunder Kubernetes Connector |
| Kemp (Progress) LoadMaster | Kubernetes integration available |
| LoxiLB | Open-source, cloud-native LB with Kubernetes Service support |
Any of these can watch the hub cluster for the relevant Services and automatically configure load balancing into the tenant network—without manual NodePort tracking or HAProxy reconfiguration.
ACM console URL bug¶
ACM displays an incorrect console URL for the hosted cluster. It shows https://console-openshift-console.apps.tenant-a.apps.ocp5.stormshift.coe.muc.redhat.com/ instead of the correct https://console-openshift-console.apps.tenant-a.coe.muc.redhat.com/. The extra .apps.ocp5.stormshift segment comes from the hub's ingress domain being appended.
Tracking: OCPBUGS-105612
Verions¶
Tested with:
| Component | Version |
|---|---|
| OpenShift | v4.21.9 |
| OpenShift Virt | v4.21.0 |
| MCE | v2.11.1 |