Phase 2: Platform Configuration
This phase configures the platform prerequisites for MaaS: Kuadrant/Authorino (auth and rate limiting), User Workload Monitoring, the GatewayClass, and the MaaS Gateway.
Apply each step in order and wait for the status gates before proceeding to the next step. The ordering matters because later resources depend on earlier ones (e.g. the Authorino CR references the TLS cert created by the Service annotation, and the Gateway requires the GatewayClass to exist).
All file paths and oc apply commands in this guide are relative to the rhoai-maas-guide repository root. Make sure you have cloned it and are working from its root directory (see Getting Started).
|
| This guide is not a replacement for the official Red Hat OpenShift AI Models as a Service documentation. It is a companion resource with opinionated Kustomize manifests and automation scripts to accelerate deployment. |
Step 1: Kuadrant and Authorino
Kuadrant provides authentication (Authorino) and rate limiting (Limitador) for MaaS API endpoints. These three configurations:
-
Create the
kuadrant-systemnamespace -
Pre-configure the Authorino Service with a service-ca annotation to auto-generate a TLS certificate (
authorino-server-cert) for secure Gateway-to-Authorino communication -
Deploy the Kuadrant CR with observability enabled, which triggers the Kuadrant operator to install Authorino and Limitador components. The
observability.enable: truesetting ensures these components expose metrics that User Workload Monitoring can scrape, which is essential for the optional observability dashboards in Phase 7.
The Kuadrant operator auto-creates an Authorino instance when reconciling the Kuadrant resource. You’ll enable TLS on that Authorino instance in the next step.
|
Apply the namespace, service annotation, and Kuadrant CR. Do not apply the full kustomization in one shot. The Kuadrant operator auto-creates an Authorino CR when it reconciles the Kuadrant resource, so the Authorino CR must be configured separately via |
oc apply -f manifests/02-platform-config/kuadrant/namespace.yaml
oc apply -f manifests/02-platform-config/kuadrant/service-annotation.yaml
oc apply -f manifests/02-platform-config/kuadrant/kuadrant.yaml
Wait for Kuadrant to become ready:
oc wait --for=condition=Ready kuadrant/kuadrant -n kuadrant-system --timeout=120s
|
Troubleshooting: If Kuadrant reports
|
Step 2: Configure TLS for Models-as-a-Service
Now that Kuadrant is deployed, we need to update the Authorino CR to enable TLS. (Detailed documentation for this step is available in the RHOAI 3.4 docs section 1.4. ) Follow the four steps below to enable TLS between the Gateway and Authorino.
Step 2a: The service annotation (already applied above) triggered the service-ca operator to generate the authorino-server-cert TLS Secret:
oc get secret authorino-server-cert -n kuadrant-system
Step 2b: Patch the Authorino CR to enable the TLS listener:
oc patch authorino authorino -n kuadrant-system --type=merge --patch '{
"spec": {
"listener": {
"tls": {
"enabled": true,
"certSecretRef": {
"name": "authorino-server-cert"
}
}
}
}
}'
Step 2c: Configure Authorino deployment with TLS certificate env vars:
oc -n kuadrant-system set env deployment/authorino \
SSL_CERT_FILE=/etc/ssl/certs/openshift-service-ca/service-ca-bundle.crt \
REQUESTS_CA_BUNDLE=/etc/ssl/certs/openshift-service-ca/service-ca-bundle.crt
Wait for the Authorino deployment to become available:
oc wait --for=condition=Available deployment/authorino -n kuadrant-system --timeout=300s
Step 3: User Workload Monitoring (UWM)
Enable User Workload Monitoring so that Prometheus scrapes MaaS and Kuadrant metrics from user namespaces.
oc apply -k manifests/02-platform-config/uwm/
Wait for the user-workload monitoring stack to start:
The prometheus-operator deployment may take 10-20 seconds to appear after applying the ConfigMap. If the wait command returns "not found", retry after a few seconds.
|
oc wait --for=condition=Available deployment/prometheus-operator \
-n openshift-user-workload-monitoring --timeout=300s
Verify the Prometheus pods are running:
oc get pods -n openshift-user-workload-monitoring
You should see prometheus-user-workload-0 and thanos-ruler-user-workload-0 pods in Running state.
Expected output:
NAME READY STATUS RESTARTS AGE
prometheus-operator-76b9c6d5dc-rrvn8 2/2 Running 0 23m
prometheus-user-workload-0 6/6 Running 0 23m
thanos-ruler-user-workload-0 4/4 Running 0 23m
Step 4: GatewayClass
Apply the GatewayClass that initializes OpenShift’s built-in Gateway API controller:
oc apply -f manifests/02-platform-config/gatewayclass.yaml
Wait for the GatewayClass to be accepted:
oc wait --for=condition=Accepted gatewayclass/openshift-default --timeout=120s
Verify:
oc get gatewayclass openshift-default
Expected output:
NAME CONTROLLER ACCEPTED AGE
openshift-default openshift.io/gateway-controller/v1 True ...
Step 5: MaaS Gateway
The Gateway uses cluster-specific values (domain and TLS cert name), so it is provided as an envsubst template. Here we will extract the values, render the template, and apply them.
Step 5a: Check Your Platform Type
Before creating the Gateway, determine if your cluster requires additional configuration for non-cloud platforms:
oc get infrastructure cluster -o jsonpath='{.status.platform}'
| Platform output | What to do |
|---|---|
|
Skip to Step 5b — create the Gateway directly. |
|
Continue to Step 5a.1 — verify MetalLB is installed first. |
Step 5a.1: Verify MetalLB is Installed (Non-Cloud Platforms Only)
Check if MetalLB is installed:
oc get deployment metallb-operator-controller-manager -n metallb-system 2>/dev/null
If you see "NotFound" or "Error":
|
MetalLB is not installed. Without it, the Gateway will never reach You must go back and complete: Phase 1: MetalLB for Non-Cloud Clusters After installing MetalLB and configuring an IPAddressPool, return to this step. |
If MetalLB is installed: Continue to the next step.
Step 5b: Create the MaaS Gateway
First, apply the Gateway resource overrides ConfigMap. This sets the gateway proxy memory limit to 2Gi (the Istio default of 1Gi is insufficient when Kuadrant Wasm extensions are loaded):
oc apply -f manifests/02-platform-config/gateway-resources.yaml
Now create the Gateway. The Gateway template includes spec.infrastructure.parametersRef pointing to the ConfigMap above, so Istio applies the 2Gi limit to the generated Deployment and preserves it across reconciliations.
export CLUSTER_DOMAIN=$(oc get ingresses.config.openshift.io cluster \
-o jsonpath='{.spec.domain}')
echo $CLUSTER_DOMAIN
export CERT_NAME=$(oc get ingresscontroller default \
-n openshift-ingress-operator \
-o jsonpath='{.spec.defaultCertificate.name}' 2>/dev/null)
export CERT_NAME="${CERT_NAME:-router-certs-default}"
echo $CERT_NAME
envsubst '${CLUSTER_DOMAIN} ${CERT_NAME}' < manifests/02-platform-config/gateway.yaml.tmpl | oc apply -f -
Step 5c: Annotate the Gateway for Authorino TLS bootstrap:
oc annotate gateway maas-default-gateway -n openshift-ingress \
security.opendatahub.io/authorino-tls-bootstrap="true" --overwrite
Wait for the Gateway to be programmed:
oc wait --for=condition=Programmed gateway/maas-default-gateway \
-n openshift-ingress --timeout=120s
|
Gateway Pod OOMKill Prevention The ConfigMap applied in Step 5b overrides the Istio default If you deployed the Gateway without the ConfigMap (e.g. from an older version of this guide), apply the fix retroactively:
|
Step 5d: Label namespaces for Gateway route binding
The Gateway restricts HTTPRoute attachment to namespaces explicitly labeled with maas.opendatahub.io/gateway-access=true. This follows the principle of least privilege - only namespaces that are designated to expose services through the Gateway can create routes.
| For additional Gateway hardening beyond namespace selectors - including RBAC restrictions on HTTPRoute creation, ValidatingAdmissionPolicy enforcement, and audit procedures - see the KCS How to verify and secure configuration of OpenShift AI Gateway for Model Serving. |
|
Any namespace that needs to serve models through the MaaS Gateway must be labeled with If you create additional namespaces for model serving, remember to apply this label:
|
Label the redhat-ods-applications namespace where the MaaS API route is created:
oc label namespace redhat-ods-applications \
maas.opendatahub.io/gateway-access=true --overwrite
Model namespaces (llm, external-models) are labeled in their respective deployment phases.
|
Step 5e: Create Passthrough Route (Non-Cloud Platforms Only)
If your platform type from Step 5a was None, BareMetal, or OpenStack, you must create a passthrough Route. This routes external traffic through the OpenShift ingress controller to the Gateway’s LoadBalancer service.
If your platform type from Step 5a was AWS, Azure, GCP, or another cloud provider, you can skip this step.
|
export CLUSTER_DOMAIN=$(oc get ingresses.config.openshift.io cluster \
-o jsonpath='{.spec.domain}')
envsubst '${CLUSTER_DOMAIN}' < manifests/03-maas-platform/openshift-gateway-setup/route.yaml.tmpl | oc apply -f -
|
Why is this needed? On non-cloud platforms, the Gateway’s LoadBalancer service receives an IP from MetalLB (e.g., 10.10.10.11), but that IP is only routable within the cluster network. External traffic cannot reach it directly. The passthrough Route bridges external requests (via the OpenShift router at your cluster’s public DNS) to the internal LoadBalancer IP, allowing the Gateway to be accessible from outside the cluster. |
Verify the Route was created:
oc get route maas-default-gateway-https -n openshift-ingress
Expected output:
NAME HOST/PORT PATH SERVICES PORT TERMINATION WILDCARD
maas-default-gateway-https maas.apps.<cluster-domain> maas-default-gateway-openshift-default https passthrough/Redirect None
Verification
After completing all steps, confirm the full platform state:
On macOS, the local DNS resolver may cache negative lookups for maas.apps.<cluster-domain>. If curl reports "Could not resolve host" but dig or nslookup resolves the address correctly, either flush your DNS cache (sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder) or use the --resolve flag: curl -vsk --resolve "maas.${CLUSTER_DOMAIN}:443:$(dig +short maas.${CLUSTER_DOMAIN} | head -1)" https://maas.${CLUSTER_DOMAIN}.
|
# Kuadrant ready
oc get kuadrant -n kuadrant-system
# Authorino running with TLS
oc get deployment authorino -n kuadrant-system
oc get secret authorino-server-cert -n kuadrant-system
# UWM running
oc get pods -n openshift-user-workload-monitoring
# GatewayClass accepted
oc get gatewayclass openshift-default
# Gateway programmed
oc get gateway maas-default-gateway -n openshift-ingress
# Check passthrough route exists (bare metal, Open Stack or None platform type)
oc get route maas-default-gateway-https -n openshift-ingress -o jsonpath='{.status.ingress[0].conditions[?(@.type=="Admitted")].status}'
# Verify TLS connection to the Gateway
CLUSTER_DOMAIN=$(oc get ingresses.config.openshift.io cluster -o jsonpath='{.spec.domain}')
echo $CLUSTER_DOMAIN
curl -vsk https://maas.${CLUSTER_DOMAIN} 2>&1 | grep -E "SSL connection|Connected"
Expected output:
$ oc get kuadrant -n kuadrant-system
NAME MTLS AUTHORINO MTLS LIMITADOR AGE
kuadrant false false 49m
$ oc get deployment authorino -n kuadrant-system
oc get secret authorino-server-cert -n kuadrant-system
NAME READY UP-TO-DATE AVAILABLE AGE
authorino 1/1 1 1 49m
NAME TYPE DATA AGE
authorino-server-cert kubernetes.io/tls 2 49m
$ oc get pods -n openshift-user-workload-monitoring
NAME READY STATUS RESTARTS AGE
prometheus-operator-76b9c6d5dc-rrvn8 2/2 Running 0 23m
prometheus-user-workload-0 6/6 Running 0 23m
thanos-ruler-user-workload-0 4/4 Running 0 23m
$ oc get gatewayclass openshift-default
NAME CONTROLLER ACCEPTED AGE
openshift-default openshift.io/gateway-controller/v1 True 23m
$ oc get gateway maas-default-gateway -n openshift-ingress
NAME CLASS ADDRESS PROGRAMMED AGE
maas-default-gateway openshift-default 10.10.10.11 True 13m
# (If you created a passthrough route in Step 5e)
$ oc get route maas-default-gateway-https -n openshift-ingress -o jsonpath='{.status.ingress[0].conditions[?(@.type=="Admitted")].status}'
True
$ CLUSTER_DOMAIN=$(oc get ingresses.config.openshift.io cluster -o jsonpath='{.spec.domain}')
$ echo $CLUSTER_DOMAIN
apps.`<your cluster name>`.`<your domain>`
curl -vsk https://maas.${CLUSTER_DOMAIN} 2>&1 | grep -E "SSL connection|Connected"
* Connected to maas.apps.<cluster-domain> (...) port 443
* SSL connection using TLSv1.3 / ... <-- exact cipher varies by client
Appendix
Directory Structure
manifests/02-platform-config/
gateway-resources.yaml # ConfigMap: gateway proxy resource overrides (2Gi memory)
gateway.yaml.tmpl # Gateway template (envsubst, references ConfigMap via parametersRef)
gatewayclass.yaml # GatewayClass resource
kustomization.yaml # Aggregates all subdirectories
kuadrant/
namespace.yaml # kuadrant-system namespace
service-annotation.yaml # TLS cert annotation for Authorino
kuadrant.yaml # Kuadrant CR
authorino.yaml # Authorino TLS patch
kustomization.yaml
uwm/
cluster-monitoring-config.yaml # User Workload Monitoring ConfigMap
kustomization.yaml
Next step
Proceed to Phase 3: MaaS Platform.