Skip to main content

Multi-Cluster Portal

Configure a centralized API Developer Portal on a parent cluster that aggregates and publishes APIs from multiple child clusters running in API Gateway mode.


Overview

Multi-cluster portal architecture enables you to centralize API management on a parent cluster while distributing API workloads across multiple child clusters. The parent cluster runs in API Management mode, hosting the portal, API catalog, and access control, while child clusters run in API Gateway mode, exposing backend services through uplinks.

This architecture is ideal for organizations managing APIs across different environments (dev, staging, prod), regions, or business units, where you want a single portal for discovery and management while keeping workloads distributed.

Key Benefits:

  • Centralized Management: All APIM resources (portal, APIs, plans, catalog) on parent cluster
  • Distributed Workloads: Backend APIs deployed across multiple child clusters
  • Simplified Operations: Children run in API Gateway mode (no APIM license required on children)
  • Environment Isolation: Each child cluster maintains independence while exposing services

Architecture

Configuration Example

Parent Cluster Setup

The parent cluster hosts all API Management resources and connects to child clusters via the multi-cluster provider.

1. Enable Multi-Cluster Provider

Configure the multi-cluster provider with child cluster addresses:

parent-helm-values.yaml
hub:
providers:
multicluster:
enabled: true
pollInterval: 5
pollTimeout: 5
children:
cluster-a:
address: "https://child-a.example.com:9443"
serversTransport:
insecureSkipVerify: true # Use proper mTLS in production
cluster-b:
address: "https://child-b.example.com:9443"
serversTransport:
insecureSkipVerify: true

2. Create API Resources

Define API resources that reference multi-cluster services exposed by child clusters:

crm-api-route.yaml
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: crm-api
namespace: apps
annotations:
hub.traefik.io/api: crm-api
spec:
entryPoints:
- websecure
routes:
- match: Host(`api.example.com`) && PathPrefix(`/crm`)
kind: Rule
services:
- name: crm@multicluster
kind: TraefikService
tls: {}
billing-api-route.yaml
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: billing-api
namespace: apps
annotations:
hub.traefik.io/api: billing-api
spec:
entryPoints:
- websecure
routes:
- match: Host(`api.example.com`) && PathPrefix(`/billing`)
kind: Rule
services:
- name: billing@multicluster
kind: TraefikService
tls: {}

The hub.traefik.io/api annotation links each IngressRoute to its API resource. The service names crm@multicluster and billing@multicluster reference the uplinks from Child Cluster A and Child Cluster B respectively.

3. Configure Portal and Catalog

Create the API portal and catalog items to control visibility and access:

api-portal.yaml
apiVersion: hub.traefik.io/v1alpha1
kind: APIPortal
metadata:
name: company-portal
namespace: apps
spec:
title: "Company API Portal"
description: "Centralized portal for all company APIs"
trustedUrls:
- https://portal.example.com
ui:
logoUrl: https://company.example.com/logo.png
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: company-portal
namespace: apps
annotations:
hub.traefik.io/api-portal: company-portal
spec:
entryPoints:
- websecure
routes:
- match: Host(`portal.example.com`)
kind: Rule
services:
- name: apiportal
namespace: traefik
port: 9903
tls: {}

Child Cluster Setup

Child clusters run in API Gateway mode and only need uplink configuration and backend services. No APIM resources are required.

Enable the uplink entrypoint in the child cluster's Traefik configuration:

child-helm-values.yaml
hub:
providers:
multicluster:
enabled: true

ports:
multicluster:
port: 9443
uplink: true

additionalArguments:
- --hub.uplinkEntryPoints.multicluster.http.tls=true

Each child cluster exposes its API through an uplink. Child Cluster A serves the CRM API and Child Cluster B serves the Billing API.

cluster-a/crm-uplink.yaml
apiVersion: hub.traefik.io/v1alpha1
kind: Uplink
metadata:
name: crm
namespace: apps
spec:
exposeName: crm
entryPoints:
- multicluster
healthCheck:
interval: 5s
timeout: 3s
path: /crm/health
method: GET
cluster-a/crm-ingress.yaml
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: crm-api
namespace: apps
annotations:
hub.traefik.io/router.uplinks: crm
spec:
entryPoints:
- web
routes:
- match: PathPrefix(`/crm`)
kind: Rule
services:
- name: crm-service
port: 80
middlewares:
- name: strip-crm-prefix
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: strip-crm-prefix
namespace: apps
spec:
stripPrefix:
prefixes:
- /crm
cluster-a/crm-service.yaml
apiVersion: v1
kind: Service
metadata:
name: crm-service
namespace: apps
spec:
selector:
app: crm-backend
ports:
- port: 80
targetPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: crm-backend
namespace: apps
spec:
replicas: 2
selector:
matchLabels:
app: crm-backend
template:
metadata:
labels:
app: crm-backend
spec:
containers:
- name: crm-api
image: company/crm-api:latest
ports:
- containerPort: 8080
important

The IngressRoute on child clusters should not include a Host matcher when using uplinks. Use PathPrefix only to ensure proper routing from the parent cluster.

Important Considerations

APIM Licensing

  • Parent cluster: Requires Traefik Hub with API Management enabled (APIM license)
  • Child clusters: Only require API Gateway mode (no APIM license needed)

This architecture minimizes licensing costs by centralizing APIM on the parent while children run as lightweight API gateways.

OpenAPI Specification

When API resources are on the parent cluster, use URL-based OpenAPI specs (openApiSpec.url) rather than path-based specs (openApiSpec.path). The parent cluster needs to fetch the spec independently, not from the child cluster endpoints.

# ✅ Correct - URL-based spec
spec:
openApiSpec:
url: https://specs.example.com/crm-api.yaml

# ❌ Incorrect - Path-based spec won't work from parent
spec:
openApiSpec:
path: /openapi.yaml

Child Cluster Routing

Child cluster IngressRoutes should use PathPrefix matchers only, without Host matchers, to ensure proper routing through the multi-cluster provider:

# ✅ Correct - PathPrefix only
spec:
routes:
- match: PathPrefix(`/crm`)

# ❌ Incorrect - Host matcher will interfere
spec:
routes:
- match: Host(`api.example.com`) && PathPrefix(`/crm`)

The parent cluster's IngressRoute handles the Host matching.

Namespace Consistency

All APIM resources on the parent cluster (APIPortal, API, APIPlan, APICatalogItem) must be in the same namespace for the portal to discover and display them correctly.

Verification and Testing

After deploying the configuration, verify that the multi-cluster portal setup is working correctly.

On the parent cluster, verify that uplinks to child clusters are established:

# Check uplink status in Traefik logs
kubectl logs -n traefik deployment/traefik | grep -i uplink

# Or check the multi-cluster provider status
kubectl logs -n traefik deployment/traefik | grep -i multicluster

You should see logs indicating successful connections to child cluster addresses.

2. Verify API Portal Access

Test that the portal is accessible and displaying your APIs:

# Access the portal (replace with your portal URL)
curl -k https://portal.example.com

# Or open in browser
open https://portal.example.com

The portal should load and display the APIs you've published through catalog items.

CRM API

3. Test API Endpoints

Verify that API requests are routed correctly through the multi-cluster setup:

# Test the CRM API on Child Cluster A
curl -k https://api.example.com/crm/health

# Test the Billing API on Child Cluster B
curl -k https://api.example.com/billing/health

# Test with authentication if required
curl -k -H "Authorization: Bearer <token>" \
https://api.example.com/crm/employees

curl -k -H "Authorization: Bearer <token>" \
https://api.example.com/billing/invoices

4. Confirm Multi-Cluster Routing

Verify that each API is served by its respective child cluster by adding tracing headers. Add this middleware on each child cluster with the cluster name as the value:

cluster-a/backend-headers.yaml
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: backend-headers
namespace: apps
spec:
headers:
customResponseHeaders:
X-Traefik-Backend: "cluster-a"

Add the middleware to the CRM IngressRoute:

spec:
routes:
- match: PathPrefix(`/crm`)
kind: Rule
services:
- name: crm-service
port: 80
middlewares:
- name: backend-headers
- name: strip-crm-prefix

Then verify that each API returns a different cluster header:

# CRM API should return X-Traefik-Backend: cluster-a
curl -k -s -D - https://api.example.com/crm/health | grep X-Traefik-Backend

# Billing API should return X-Traefik-Backend: cluster-b
curl -k -s -D - https://api.example.com/billing/health | grep X-Traefik-Backend

5. Verify API Catalog Items

Check that APIs appear in the portal for the correct user groups:

  1. Log into the portal with a user from the configured group (e.g., developers)
  2. Verify the API appears in the catalog
  3. Check that the correct plan is associated (Standard or Premium)
  4. Test API access through the portal's built-in API client

6. Test Health Checks

If you configured health checks on uplinks, verify they're working on each child cluster:

# On Child Cluster A — CRM API
kubectl exec -n apps deployment/crm-backend -- \
curl http://localhost:8080/health

# On Child Cluster B — Billing API
kubectl exec -n apps deployment/billing-backend -- \
curl http://localhost:8080/health

Both health check endpoints should return a successful response.