Add OPA and Openshift AI

This commit is contained in:
2026-10-20 14:08:57 -04:00
parent 7df35b9dd4
commit 5b46ba7c74
27 changed files with 2634 additions and 1 deletions

View File

@@ -0,0 +1,111 @@
---
# --- OLM operators ---
# Installed in order. RHOAI is pinned to stable-3.5: the MaaS DSC layout
# (aigateway.modelsAsAService) changed between 3.4 and 3.5, so don't float on stable-3.x.
# RHCL must be 1.4.x (Kuadrant >= 1.4.2 for AuthPolicy spec.defaults.rules).
# cert-manager is a prerequisite but already installed by deploy_openshift.yml.
rhoai_maas_operators:
- name: rhcl-operator
namespace: openshift-operators
channel: stable-1.4
- name: leader-worker-set
namespace: openshift-lws-operator
channel: stable-v1.0
operator_group_target_namespaces:
- openshift-lws-operator
- name: rhods-operator
namespace: redhat-ods-operator
channel: stable-3.5
namespace_labels:
openshift.io/cluster-monitoring: "true"
rhoai_maas_wait_timeout: 900
# --- Gateway ---
# Empty = maas.<cluster ingress domain>.
rhoai_maas_hostname: ""
# The gateway gets its own single-host certificate. Don't use the *.apps wildcard:
# browsers would reuse the MaaS HTTP/2 connection for every other *.apps host
# (console, dashboards), and the MaaS gateway answers those with 404.
rhoai_maas_gateway_cert_secret: maas-gateway-tls
# cert-manager ClusterIssuer for the gateway certificate. Empty = use the existing
# rhoai_maas_gateway_cert_secret as-is and don't create a Certificate.
rhoai_maas_gateway_cert_issuer: letsencrypt-production
# No cloud LB on this cluster: run the gateway Service as ClusterIP and expose it
# through a passthrough Route on the default router. Set to LoadBalancer with MetalLB.
rhoai_maas_gateway_service_type: ClusterIP
rhoai_maas_gateway_create_route: true
# --- MaaS database ---
# PostgreSQL is provisioned by the cloudnativepg_operator role (see the playbook);
# this secret holds its credentials (type kubernetes.io/basic-auth).
rhoai_maas_db_secret_name: maas-postgres-secret
rhoai_maas_db_secret_namespace: rhoai-maas-db
rhoai_maas_db_sslmode: require
rhoai_maas_infra_namespace: redhat-ai-gateway-infra
# --- DataScienceCluster ---
# Minimal component set for MaaS. Anything not listed here is set to Removed.
rhoai_maas_dsc_name: default-dsc
rhoai_maas_dsc_managed_components:
- dashboard
- kserve
- aigateway
- ogx
rhoai_maas_dsc_removed_components:
- aipipelines
- feastoperator
- kueue
- mlflowoperator
- modelregistry
- ray
- trainer
- trainingoperator
- trustyai
- workbenches
# --- Models (ExternalModel fronting in-cluster OpenAI-compatible servers) ---
# The api_key_secret must already exist in the model namespace with key `api-key`;
# the role only labels it for MaaS. Names must not collide with existing Services
# in the namespace (the controller creates a Service per provider/model).
rhoai_maas_external_models:
- name: qwen35-9b-coder
namespace: llm-coding-assistant
display_name: Qwen3.5 9B Coder (llama.cpp)
description: Qwen3.5-9B Q5_K_M served by llama.cpp on the desktop GPU node
provider_name: llama-coder-provider
provider_type: openai
endpoint: llama-coder.llm-coding-assistant.svc.cluster.local
port: 8080
tls: false
api_key_secret: llama-coder-api-key
# llama-server serves a single model and ignores the request's model field.
target_model: Qwen3.5-9B-Q5_K_M.gguf
api_format: openai-chat
path: /v1/chat/completions
# --- Access policy and quota (CR spec shapes, models-as-a-service namespace) ---
rhoai_maas_auth_policies:
- name: llama-coder-access
display_name: llama-coder access
description: All authenticated users can call the llama-coder model
groups:
- system:authenticated
users: []
model_refs:
- name: qwen35-9b-coder
namespace: llm-coding-assistant
rhoai_maas_subscriptions:
- name: llama-coder-default
display_name: llama-coder default
description: 200k tokens/hour per user for all authenticated users
priority: 10
groups:
- system:authenticated
users: []
model_refs:
- name: qwen35-9b-coder
namespace: llm-coding-assistant
tokenRateLimits:
- limit: 200000
window: 1h

View File

@@ -0,0 +1,90 @@
---
argument_specs:
main:
short_description: Install Red Hat OpenShift AI 3.5 with Models-as-a-Service
description:
- Installs Red Hat Connectivity Link, Leader Worker Set and the RHOAI operator via OLM,
configures User Workload Monitoring, the MaaS Gateway, Kuadrant and Authorino TLS,
creates the maas-db-config secret, and enables a minimal DataScienceCluster with
aigateway.modelsAsAService Managed.
- Registers in-cluster OpenAI-compatible servers as ExternalModels (Technology Preview)
and applies MaaSAuthPolicy / MaaSSubscription resources.
- PostgreSQL is not provisioned here; run cloudnativepg_operator first (see
playbooks/deploy_rhoai_maas.yml).
options:
rhoai_maas_operators:
description: OLM operators to install, in order (name, namespace, channel, optional
operator_group_target_namespaces and namespace_labels).
type: list
elements: dict
rhoai_maas_wait_timeout:
description: Seconds to wait for each operator, CRD, and deployment to become ready.
type: int
default: 900
rhoai_maas_hostname:
description: MaaS gateway hostname. Empty uses maas.<cluster ingress domain>.
type: str
default: ""
rhoai_maas_gateway_cert_secret:
description: >-
TLS Secret in openshift-ingress used by the Gateway HTTPS listener. Must be
a single-host cert, not the *.apps wildcard (browser HTTP/2 coalescing).
type: str
default: maas-gateway-tls
rhoai_maas_gateway_cert_issuer:
description: >-
cert-manager ClusterIssuer that issues rhoai_maas_gateway_cert_secret for the
MaaS hostname. Empty skips creating the Certificate.
type: str
default: letsencrypt-production
rhoai_maas_gateway_service_type:
description: Service type for the Gateway's generated Service.
type: str
choices: [ClusterIP, LoadBalancer, NodePort]
default: ClusterIP
rhoai_maas_gateway_create_route:
description: Create a passthrough Route on the default router to the Gateway Service.
type: bool
default: true
rhoai_maas_db_secret_name:
description: basic-auth Secret (username/password/host/port/database) for the MaaS database.
type: str
default: maas-postgres-secret
rhoai_maas_db_secret_namespace:
description: Namespace of rhoai_maas_db_secret_name.
type: str
default: rhoai-maas-db
rhoai_maas_db_sslmode:
description: libpq sslmode used in DB_CONNECTION_URL.
type: str
default: require
rhoai_maas_infra_namespace:
description: MaaS infrastructure namespace that holds maas-db-config (3.5+).
type: str
default: redhat-ai-gateway-infra
rhoai_maas_dsc_name:
description: DataScienceCluster name.
type: str
default: default-dsc
rhoai_maas_dsc_managed_components:
description: DSC components set to Managed.
type: list
elements: str
rhoai_maas_dsc_removed_components:
description: DSC components set to Removed.
type: list
elements: str
rhoai_maas_external_models:
description: In-cluster or external OpenAI-compatible endpoints to register as
ExternalProvider + ExternalModel + MaaSModelRef.
type: list
elements: dict
rhoai_maas_auth_policies:
description: MaaSAuthPolicy definitions (name, groups, users, model_refs).
type: list
elements: dict
rhoai_maas_subscriptions:
description: MaaSSubscription definitions (name, priority, groups, users, model_refs
with tokenRateLimits).
type: list
elements: dict

View File

@@ -0,0 +1,18 @@
---
galaxy_info:
author: ptoal
description: Install Red Hat OpenShift AI with Models-as-a-Service and register in-cluster models
license: MIT
min_ansible_version: "2.16"
platforms:
- name: GenericLinux
versions:
- all
galaxy_tags:
- openshift
- rhoai
- maas
- operator
- olm
dependencies: []

View File

@@ -0,0 +1,49 @@
---
# Build maas-db-config (DB_CONNECTION_URL) from the CloudNativePG basic-auth secret.
# RHOAI 3.5+ reads it from the infrastructure namespace; it must exist before the
# DSC enables modelsAsAService.
- name: Read MaaS database credentials
kubernetes.core.k8s_info:
api_version: v1
kind: Secret
namespace: "{{ rhoai_maas_db_secret_namespace }}"
name: "{{ rhoai_maas_db_secret_name }}"
register: __rhoai_maas_db_secret
no_log: true
- name: Verify MaaS database credentials exist
ansible.builtin.assert:
that: __rhoai_maas_db_secret.resources | length > 0
fail_msg: >-
Secret {{ rhoai_maas_db_secret_namespace }}/{{ rhoai_maas_db_secret_name }} not found.
Run the cloudnativepg_operator role first (playbooks/deploy_rhoai_maas.yml does this).
- name: Create MaaS infrastructure namespace
kubernetes.core.k8s:
state: present
definition:
apiVersion: v1
kind: Namespace
metadata:
name: "{{ rhoai_maas_infra_namespace }}"
- name: Create maas-db-config secret
vars:
__db: "{{ __rhoai_maas_db_secret.resources[0].data }}"
kubernetes.core.k8s:
state: present
definition:
apiVersion: v1
kind: Secret
metadata:
name: maas-db-config
namespace: "{{ rhoai_maas_infra_namespace }}"
type: Opaque
stringData:
DB_CONNECTION_URL: >-
postgresql://{{ __db.username | b64decode | urlencode | replace('/', '%2F') }}:{{
__db.password | b64decode | urlencode | replace('/', '%2F') }}@{{
__db.host | b64decode }}:{{ __db.port | b64decode }}/{{ __db.database | b64decode
}}?sslmode={{ rhoai_maas_db_sslmode }}
no_log: true

View File

@@ -0,0 +1,41 @@
---
# Install Red Hat OpenShift AI 3.5 with Models-as-a-Service and register
# in-cluster OpenAI-compatible servers (llama-coder) as MaaS ExternalModels.
#
# Follows "Govern LLM access with Models-as-a-Service" (RHOAI Self-Managed 3.5)
# and its companion guide https://github.com/jharmison-redhat/rhoai-maas-guide.
#
# Order matters:
# - GatewayClass before Kuadrant, or Kuadrant reports MissingDependency
# (Istio is provisioned by the GatewayClass).
# - maas-db-config before the DSC enables modelsAsAService.
#
# All tasks are idempotent (kubernetes.core.k8s state: present / patched).
- name: Install operators
ansible.builtin.include_tasks: operator.yml
loop: "{{ rhoai_maas_operators }}"
loop_control:
loop_var: __rhoai_maas_operator
label: "{{ __rhoai_maas_operator.name }}"
- name: Configure platform (monitoring, gateway, Kuadrant)
ansible.builtin.include_tasks: platform.yml
- name: Configure MaaS database secret
ansible.builtin.include_tasks: database.yml
- name: Configure DataScienceCluster
ansible.builtin.include_tasks: rhoai.yml
- name: Register models and access policies
ansible.builtin.include_tasks: models.yml
- name: Display MaaS deployment summary
ansible.builtin.debug:
msg:
- "Models-as-a-Service ready!"
- " Endpoint : https://{{ __rhoai_maas_hostname }}/v1"
- " Models : {{ rhoai_maas_external_models | map(attribute='name') | join(', ') }}"
- " API keys : RHOAI dashboard, or POST https://{{ __rhoai_maas_hostname }}/maas-api/v1/api-keys with an OpenShift token"
- " Test : curl -H \"Authorization: Bearer $MAAS_API_KEY\" https://{{ __rhoai_maas_hostname }}/v1/models"

View File

@@ -0,0 +1,165 @@
---
# ------------------------------------------------------------------
# ExternalProvider + ExternalModel + MaaSModelRef per model
# ------------------------------------------------------------------
- name: Allow model namespaces to attach to the Gateway
kubernetes.core.k8s:
state: patched
api_version: v1
kind: Namespace
name: "{{ item }}"
definition:
metadata:
labels:
maas.opendatahub.io/gateway-access: "true"
opendatahub.io/dashboard: "true"
loop: "{{ rhoai_maas_external_models | map(attribute='namespace') | unique }}"
- name: Mark provider API key secrets as MaaS-managed
kubernetes.core.k8s:
state: patched
api_version: v1
kind: Secret
namespace: "{{ item.namespace }}"
name: "{{ item.api_key_secret }}"
definition:
metadata:
labels:
inference.llm-d.ai/ipp-managed: "true"
loop: "{{ rhoai_maas_external_models }}"
loop_control:
label: "{{ item.namespace }}/{{ item.api_key_secret }}"
- name: Create ExternalProviders
kubernetes.core.k8s:
state: present
definition:
apiVersion: inference.opendatahub.io/v1alpha1
kind: ExternalProvider
metadata:
name: "{{ item.provider_name }}"
namespace: "{{ item.namespace }}"
spec:
provider: "{{ item.provider_type }}"
endpoint: "{{ item.endpoint }}"
auth:
type: apikey
secretRef:
name: "{{ item.api_key_secret }}"
loop: "{{ rhoai_maas_external_models }}"
loop_control:
label: "{{ item.namespace }}/{{ item.provider_name }}"
- name: Wait for ExternalProviders to be ready
kubernetes.core.k8s_info:
api_version: inference.opendatahub.io/v1alpha1
kind: ExternalProvider
namespace: "{{ item.namespace }}"
name: "{{ item.provider_name }}"
register: __rhoai_maas_provider
until: (__rhoai_maas_provider.resources[0].status.phase | default('')) == 'Ready'
retries: 30
delay: 10
loop: "{{ rhoai_maas_external_models }}"
loop_control:
label: "{{ item.namespace }}/{{ item.provider_name }}"
- name: Create ExternalModels
kubernetes.core.k8s:
state: present
definition:
apiVersion: inference.opendatahub.io/v1alpha1
kind: ExternalModel
metadata:
name: "{{ item.name }}"
namespace: "{{ item.namespace }}"
annotations:
maas.opendatahub.io/tls: "{{ item.tls | default(true) | string | lower }}"
maas.opendatahub.io/port: "{{ item.port | default(443) | string }}"
spec:
externalProviderRefs:
- ref:
name: "{{ item.provider_name }}"
targetModel: "{{ item.target_model }}"
apiFormat: "{{ item.api_format | default('openai-chat') }}"
path: "{{ item.path | default('/v1/chat/completions') }}"
weight: 100
loop: "{{ rhoai_maas_external_models }}"
loop_control:
label: "{{ item.namespace }}/{{ item.name }}"
- name: Publish models to MaaS
kubernetes.core.k8s:
state: present
definition:
apiVersion: maas.opendatahub.io/v1alpha1
kind: MaaSModelRef
metadata:
name: "{{ item.name }}"
namespace: "{{ item.namespace }}"
annotations:
openshift.io/display-name: "{{ item.display_name | default(item.name) }}"
openshift.io/description: "{{ item.description | default('') }}"
spec:
modelRef:
kind: ExternalModel
name: "{{ item.name }}"
loop: "{{ rhoai_maas_external_models }}"
loop_control:
label: "{{ item.namespace }}/{{ item.name }}"
# ------------------------------------------------------------------
# Access policies and quota (models-as-a-service namespace)
# ------------------------------------------------------------------
- name: Wait for the models-as-a-service namespace
kubernetes.core.k8s_info:
api_version: v1
kind: Namespace
name: "{{ __rhoai_maas_namespace }}"
register: __rhoai_maas_ns
until: __rhoai_maas_ns.resources | length > 0
retries: "{{ __rhoai_maas_wait_retries }}"
delay: 10
- name: Create MaaSAuthPolicies
kubernetes.core.k8s:
state: present
definition:
apiVersion: maas.opendatahub.io/v1alpha1
kind: MaaSAuthPolicy
metadata:
name: "{{ item.name }}"
namespace: "{{ __rhoai_maas_namespace }}"
annotations:
openshift.io/display-name: "{{ item.display_name | default(item.name) }}"
openshift.io/description: "{{ item.description | default('') }}"
spec:
modelRefs: "{{ item.model_refs }}"
subjects:
groups: "{{ item.groups | default([]) | map('community.general.dict_kv', 'name') | list }}"
users: "{{ item.users | default([]) }}"
loop: "{{ rhoai_maas_auth_policies }}"
loop_control:
label: "{{ item.name }}"
- name: Create MaaSSubscriptions
kubernetes.core.k8s:
state: present
definition:
apiVersion: maas.opendatahub.io/v1alpha1
kind: MaaSSubscription
metadata:
name: "{{ item.name }}"
namespace: "{{ __rhoai_maas_namespace }}"
annotations:
openshift.io/display-name: "{{ item.display_name | default(item.name) }}"
openshift.io/description: "{{ item.description | default('') }}"
spec:
owner:
groups: "{{ item.groups | default([]) | map('community.general.dict_kv', 'name') | list }}"
users: "{{ item.users | default([]) }}"
modelRefs: "{{ item.model_refs }}"
priority: "{{ item.priority | default(10) | int }}"
loop: "{{ rhoai_maas_subscriptions }}"
loop_control:
label: "{{ item.name }}"

View File

@@ -0,0 +1,69 @@
---
# Install one OLM operator (Automatic approval) and wait for its CSV.
# Expects __rhoai_maas_operator: {name, namespace, channel,
# operator_group_target_namespaces?, namespace_labels?}
- name: Create operator namespace {{ __rhoai_maas_operator.namespace }}
kubernetes.core.k8s:
state: present
definition:
apiVersion: v1
kind: Namespace
metadata:
name: "{{ __rhoai_maas_operator.namespace }}"
labels: "{{ __rhoai_maas_operator.namespace_labels | default({}) }}"
# openshift-operators already has the global-operators OperatorGroup
- name: Create OperatorGroup for {{ __rhoai_maas_operator.name }}
kubernetes.core.k8s:
state: present
definition:
apiVersion: operators.coreos.com/v1
kind: OperatorGroup
metadata:
name: "{{ __rhoai_maas_operator.name }}"
namespace: "{{ __rhoai_maas_operator.namespace }}"
spec: >-
{{ {'targetNamespaces': __rhoai_maas_operator.operator_group_target_namespaces}
if __rhoai_maas_operator.operator_group_target_namespaces is defined else {} }}
when: __rhoai_maas_operator.namespace != 'openshift-operators'
- name: Subscribe to {{ __rhoai_maas_operator.name }}
kubernetes.core.k8s:
state: present
definition:
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
name: "{{ __rhoai_maas_operator.name }}"
namespace: "{{ __rhoai_maas_operator.namespace }}"
spec:
channel: "{{ __rhoai_maas_operator.channel }}"
installPlanApproval: Automatic
name: "{{ __rhoai_maas_operator.name }}"
source: "{{ __rhoai_maas_operator.source | default('redhat-operators') }}"
sourceNamespace: openshift-marketplace
- name: Wait for Subscription to report an installed CSV - {{ __rhoai_maas_operator.name }}
kubernetes.core.k8s_info:
api_version: operators.coreos.com/v1alpha1
kind: Subscription
namespace: "{{ __rhoai_maas_operator.namespace }}"
name: "{{ __rhoai_maas_operator.name }}"
register: __rhoai_maas_sub
until: (__rhoai_maas_sub.resources[0].status.installedCSV | default('')) | length > 0
retries: "{{ __rhoai_maas_wait_retries }}"
delay: 10
- name: Wait for CSV to succeed - {{ __rhoai_maas_operator.name }}
kubernetes.core.k8s_info:
api_version: operators.coreos.com/v1alpha1
kind: ClusterServiceVersion
namespace: "{{ __rhoai_maas_operator.namespace }}"
name: "{{ __rhoai_maas_sub.resources[0].status.installedCSV }}"
register: __rhoai_maas_csv
until: >-
__rhoai_maas_csv.resources | length > 0 and
__rhoai_maas_csv.resources[0].status.phase | default('') == 'Succeeded'
retries: "{{ __rhoai_maas_wait_retries }}"
delay: 10

View File

@@ -0,0 +1,361 @@
---
# ------------------------------------------------------------------
# User Workload Monitoring (MaaS reports Degraded without it)
# ------------------------------------------------------------------
- name: Read cluster monitoring config
kubernetes.core.k8s_info:
api_version: v1
kind: ConfigMap
namespace: openshift-monitoring
name: cluster-monitoring-config
register: __rhoai_maas_cmo
- name: Resolve cluster monitoring config
ansible.builtin.set_fact:
__rhoai_maas_cmo_config: >-
{{ ((__rhoai_maas_cmo.resources[0].data['config.yaml'] | default('{}')) if __rhoai_maas_cmo.resources else '{}')
| from_yaml | default({}, true) }}
- name: Enable User Workload Monitoring
kubernetes.core.k8s:
state: present
definition:
apiVersion: v1
kind: ConfigMap
metadata:
name: cluster-monitoring-config
namespace: openshift-monitoring
data:
config.yaml: "{{ __rhoai_maas_cmo_config | combine({'enableUserWorkload': true}) | to_nice_yaml }}"
when: not (__rhoai_maas_cmo_config.enableUserWorkload | default(false) | bool)
# ------------------------------------------------------------------
# Gateway API: GatewayClass, Gateway, passthrough Route
# ------------------------------------------------------------------
- name: Read cluster ingress domain
kubernetes.core.k8s_info:
api_version: config.openshift.io/v1
kind: Ingress
name: cluster
register: __rhoai_maas_ingress
when: rhoai_maas_hostname | length == 0
- name: Resolve MaaS hostname
ansible.builtin.set_fact:
__rhoai_maas_hostname: >-
{{ rhoai_maas_hostname if rhoai_maas_hostname | length > 0
else 'maas.' ~ __rhoai_maas_ingress.resources[0].spec.domain }}
- name: Create MaaS gateway certificate
kubernetes.core.k8s:
state: present
definition:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: "{{ __rhoai_maas_gateway_name }}-cert"
namespace: "{{ __rhoai_maas_gateway_namespace }}"
spec:
secretName: "{{ rhoai_maas_gateway_cert_secret }}"
issuerRef:
name: "{{ rhoai_maas_gateway_cert_issuer }}"
kind: ClusterIssuer
dnsNames:
- "{{ __rhoai_maas_hostname }}"
duration: 2160h
renewBefore: 720h
when: rhoai_maas_gateway_cert_issuer | length > 0
- name: Wait for MaaS gateway certificate to be issued
kubernetes.core.k8s_info:
api_version: cert-manager.io/v1
kind: Certificate
namespace: "{{ __rhoai_maas_gateway_namespace }}"
name: "{{ __rhoai_maas_gateway_name }}-cert"
register: __rhoai_maas_gw_cert
until: >-
(__rhoai_maas_gw_cert.resources[0].status.conditions | default([])
| selectattr('type', 'equalto', 'Ready') | selectattr('status', 'equalto', 'True') | list | length) > 0
retries: 30
delay: 10
when: rhoai_maas_gateway_cert_issuer | length > 0
# Creating the GatewayClass makes the ingress operator install Service Mesh 3 (Istio)
- name: Create openshift-default GatewayClass
kubernetes.core.k8s:
state: present
definition:
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: openshift-default
spec:
controllerName: openshift.io/gateway-controller/v1
- name: Wait for GatewayClass to be accepted
kubernetes.core.k8s_info:
api_version: gateway.networking.k8s.io/v1
kind: GatewayClass
name: openshift-default
register: __rhoai_maas_gwc
until: >-
__rhoai_maas_gwc.resources | length > 0 and
(__rhoai_maas_gwc.resources[0].status.conditions | default([])
| selectattr('type', 'equalto', 'Accepted') | selectattr('status', 'equalto', 'True') | list | length) > 0
retries: "{{ __rhoai_maas_wait_retries }}"
delay: 10
- name: Create Gateway deployment options
kubernetes.core.k8s:
state: present
definition:
apiVersion: v1
kind: ConfigMap
metadata:
name: maas-gateway-options
namespace: "{{ __rhoai_maas_gateway_namespace }}"
data:
deployment: |
spec:
template:
spec:
containers:
- name: istio-proxy
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: "2"
memory: 2Gi
- name: Create MaaS Gateway
kubernetes.core.k8s:
state: present
definition:
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: "{{ __rhoai_maas_gateway_name }}"
namespace: "{{ __rhoai_maas_gateway_namespace }}"
annotations:
opendatahub.io/managed: "false"
security.opendatahub.io/authorino-tls-bootstrap: "true"
networking.istio.io/service-type: "{{ rhoai_maas_gateway_service_type }}"
spec:
gatewayClassName: openshift-default
infrastructure:
parametersRef:
group: ""
kind: ConfigMap
name: maas-gateway-options
listeners:
- name: http
hostname: "{{ __rhoai_maas_hostname }}"
port: 80
protocol: HTTP
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
maas.opendatahub.io/gateway-access: "true"
- name: https
hostname: "{{ __rhoai_maas_hostname }}"
port: 443
protocol: HTTPS
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
maas.opendatahub.io/gateway-access: "true"
tls:
mode: Terminate
certificateRefs:
- group: ""
kind: Secret
name: "{{ rhoai_maas_gateway_cert_secret }}"
- name: Wait for MaaS Gateway to be programmed
kubernetes.core.k8s_info:
api_version: gateway.networking.k8s.io/v1
kind: Gateway
namespace: "{{ __rhoai_maas_gateway_namespace }}"
name: "{{ __rhoai_maas_gateway_name }}"
register: __rhoai_maas_gw
until: >-
(__rhoai_maas_gw.resources[0].status.conditions | default([])
| selectattr('type', 'equalto', 'Programmed') | selectattr('status', 'equalto', 'True') | list | length) > 0
retries: "{{ __rhoai_maas_wait_retries }}"
delay: 10
- name: Expose MaaS Gateway through the default router
kubernetes.core.k8s:
state: present
definition:
apiVersion: route.openshift.io/v1
kind: Route
metadata:
name: "{{ __rhoai_maas_gateway_name }}-https"
namespace: "{{ __rhoai_maas_gateway_namespace }}"
spec:
host: "{{ __rhoai_maas_hostname }}"
port:
targetPort: https
tls:
termination: passthrough
insecureEdgeTerminationPolicy: Redirect
to:
kind: Service
name: "{{ __rhoai_maas_gateway_service }}"
weight: 100
when: rhoai_maas_gateway_create_route | bool
# ------------------------------------------------------------------
# Kuadrant (Authorino + Limitador) and Authorino TLS
# ------------------------------------------------------------------
- name: Create kuadrant-system namespace
kubernetes.core.k8s:
state: present
definition:
apiVersion: v1
kind: Namespace
metadata:
name: kuadrant-system
- name: Create Kuadrant instance
kubernetes.core.k8s:
state: present
definition:
apiVersion: kuadrant.io/v1beta1
kind: Kuadrant
metadata:
name: kuadrant
namespace: kuadrant-system
spec:
observability:
enable: true
# The Kuadrant operator only detects the Gateway API provider (Istio) at startup.
# RHCL is installed before the GatewayClass provisions Istio, so on a fresh
# install Kuadrant reports MissingDependency until the operator pod restarts.
- name: Read Kuadrant status
kubernetes.core.k8s_info:
api_version: kuadrant.io/v1beta1
kind: Kuadrant
namespace: kuadrant-system
name: kuadrant
register: __rhoai_maas_kuadrant
until: >-
(__rhoai_maas_kuadrant.resources[0].status.conditions | default([])
| selectattr('type', 'equalto', 'Ready') | list | length) > 0
retries: 12
delay: 5
failed_when: false
- name: Restart the Kuadrant operator to detect Istio
kubernetes.core.k8s:
state: absent
api_version: v1
kind: Pod
namespace: openshift-operators
label_selectors:
- app=kuadrant
- control-plane=controller-manager
when: >-
(__rhoai_maas_kuadrant.resources[0].status.conditions | default([])
| selectattr('type', 'equalto', 'Ready') | map(attribute='reason') | first | default(''))
== 'MissingDependency'
- name: Wait for Kuadrant to be ready
kubernetes.core.k8s_info:
api_version: kuadrant.io/v1beta1
kind: Kuadrant
namespace: kuadrant-system
name: kuadrant
register: __rhoai_maas_kuadrant
until: >-
(__rhoai_maas_kuadrant.resources[0].status.conditions | default([])
| selectattr('type', 'equalto', 'Ready') | selectattr('status', 'equalto', 'True') | list | length) > 0
retries: "{{ __rhoai_maas_wait_retries }}"
delay: 10
- name: Wait for Authorino service
kubernetes.core.k8s_info:
api_version: v1
kind: Service
namespace: kuadrant-system
name: authorino-authorino-authorization
register: __rhoai_maas_authorino_svc
until: __rhoai_maas_authorino_svc.resources | length > 0
retries: "{{ __rhoai_maas_wait_retries }}"
delay: 10
- name: Request a service-ca serving certificate for Authorino
kubernetes.core.k8s:
state: patched
api_version: v1
kind: Service
namespace: kuadrant-system
name: authorino-authorino-authorization
definition:
metadata:
annotations:
service.beta.openshift.io/serving-cert-secret-name: authorino-server-cert
- name: Wait for Authorino serving certificate
kubernetes.core.k8s_info:
api_version: v1
kind: Secret
namespace: kuadrant-system
name: authorino-server-cert
register: __rhoai_maas_authorino_cert
until: __rhoai_maas_authorino_cert.resources | length > 0
retries: 30
delay: 10
no_log: true
- name: Enable Authorino TLS listener
kubernetes.core.k8s:
state: patched
api_version: operator.authorino.kuadrant.io/v1beta1
kind: Authorino
namespace: kuadrant-system
name: authorino
merge_type: merge
definition:
spec:
listener:
tls:
enabled: true
certSecretRef:
name: authorino-server-cert
- name: Read Authorino deployment
kubernetes.core.k8s_info:
api_version: apps/v1
kind: Deployment
namespace: kuadrant-system
name: authorino
register: __rhoai_maas_authorino_deploy
- name: Trust the service CA in Authorino
kubernetes.core.k8s:
state: patched
api_version: apps/v1
kind: Deployment
namespace: kuadrant-system
name: authorino
merge_type: strategic-merge
definition:
spec:
template:
spec:
containers:
- name: "{{ __rhoai_maas_authorino_deploy.resources[0].spec.template.spec.containers[0].name }}"
env:
- name: SSL_CERT_FILE
value: /etc/ssl/certs/openshift-service-ca/service-ca-bundle.crt
- name: REQUESTS_CA_BUNDLE
value: /etc/ssl/certs/openshift-service-ca/service-ca-bundle.crt

View File

@@ -0,0 +1,143 @@
---
- name: Wait for RHOAI CRDs
kubernetes.core.k8s_info:
api_version: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
name: "{{ item }}"
register: __rhoai_maas_crd
until: >-
__rhoai_maas_crd.resources | length > 0 and
(__rhoai_maas_crd.resources[0].status.conditions | default([])
| selectattr('type', 'equalto', 'Established') | selectattr('status', 'equalto', 'True') | list | length) > 0
retries: "{{ __rhoai_maas_wait_retries }}"
delay: 10
loop:
- dscinitializations.dscinitialization.opendatahub.io
- datascienceclusters.datasciencecluster.opendatahub.io
- name: Configure DSCInitialization
kubernetes.core.k8s:
state: present
definition:
apiVersion: dscinitialization.opendatahub.io/v2
kind: DSCInitialization
metadata:
name: default-dsci
spec:
applicationsNamespace: "{{ __rhoai_maas_apps_namespace }}"
monitoring:
managementState: Managed
trustedCABundle:
managementState: Managed
customCABundle: ""
register: __rhoai_maas_dsci_result
# The operator webhook may not be serving yet right after the CSV succeeds
until: __rhoai_maas_dsci_result is not failed
retries: 30
delay: 10
- name: Resolve DataScienceCluster components
ansible.builtin.set_fact:
__rhoai_maas_dsc_components: >-
{{ dict(rhoai_maas_dsc_managed_components | product([{'managementState': 'Managed'}]))
| combine(dict(rhoai_maas_dsc_removed_components | product([{'managementState': 'Removed'}])))
| combine(__rhoai_maas_dsc_component_overrides, recursive=True) }}
- name: Configure DataScienceCluster
kubernetes.core.k8s:
state: present
definition:
apiVersion: datasciencecluster.opendatahub.io/v2
kind: DataScienceCluster
metadata:
name: "{{ rhoai_maas_dsc_name }}"
spec:
components: "{{ __rhoai_maas_dsc_components }}"
register: __rhoai_maas_dsc_result
until: __rhoai_maas_dsc_result is not failed
retries: 30
delay: 10
# The API server silently prunes unknown fields, so a misspelled component
# setting applies cleanly but leaves MaaS Removed and its CRDs never appear.
- name: Verify MaaS is enabled in the DataScienceCluster
ansible.builtin.assert:
that:
- >-
(__rhoai_maas_dsc_result.result.spec.components.aigateway.modelsAsAService.managementState
| default('')) == 'Managed'
fail_msg: >-
DataScienceCluster {{ rhoai_maas_dsc_name }} does not have
aigateway.modelsAsAService Managed after apply (field pruned or overridden):
{{ __rhoai_maas_dsc_result.result.spec.components.aigateway | default({}) }}
- name: Wait for MaaS CRDs
kubernetes.core.k8s_info:
api_version: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
name: "{{ item }}"
register: __rhoai_maas_crd
until: >-
__rhoai_maas_crd.resources | length > 0 and
(__rhoai_maas_crd.resources[0].status.conditions | default([])
| selectattr('type', 'equalto', 'Established') | selectattr('status', 'equalto', 'True') | list | length) > 0
retries: "{{ __rhoai_maas_wait_retries }}"
delay: 10
loop:
- maasmodelrefs.maas.opendatahub.io
- maasauthpolicies.maas.opendatahub.io
- maassubscriptions.maas.opendatahub.io
- externalproviders.inference.opendatahub.io
- externalmodels.inference.opendatahub.io
- odhdashboardconfigs.opendatahub.io
# The operator normally labels this itself; keep it as a safety net
- name: Allow the MaaS infrastructure namespace to attach to the Gateway
kubernetes.core.k8s:
state: patched
api_version: v1
kind: Namespace
name: "{{ rhoai_maas_infra_namespace }}"
definition:
metadata:
labels:
maas.opendatahub.io/gateway-access: "true"
- name: Wait for maas-api to be available
kubernetes.core.k8s_info:
api_version: apps/v1
kind: Deployment
namespace: "{{ rhoai_maas_infra_namespace }}"
name: maas-api
register: __rhoai_maas_api
until: >-
__rhoai_maas_api.resources | length > 0 and
(__rhoai_maas_api.resources[0].status.availableReplicas | default(0)) >= 1
retries: "{{ __rhoai_maas_wait_retries }}"
delay: 10
- name: Wait for the dashboard config
kubernetes.core.k8s_info:
api_version: opendatahub.io/v1alpha
kind: OdhDashboardConfig
namespace: "{{ __rhoai_maas_apps_namespace }}"
name: odh-dashboard-config
register: __rhoai_maas_dashboard
until: __rhoai_maas_dashboard.resources | length > 0
retries: "{{ __rhoai_maas_wait_retries }}"
delay: 10
- name: Enable MaaS and external models in the dashboard
kubernetes.core.k8s:
state: patched
api_version: opendatahub.io/v1alpha
kind: OdhDashboardConfig
namespace: "{{ __rhoai_maas_apps_namespace }}"
name: odh-dashboard-config
merge_type: merge
definition:
spec:
dashboardConfig:
modelAsService: true
externalModels: true
genAiStudio: true

View File

@@ -0,0 +1,19 @@
---
# Computed internal variables - do not override
__rhoai_maas_wait_retries: "{{ (rhoai_maas_wait_timeout / 10) | int }}"
__rhoai_maas_apps_namespace: redhat-ods-applications
__rhoai_maas_namespace: models-as-a-service
__rhoai_maas_gateway_name: maas-default-gateway
__rhoai_maas_gateway_namespace: openshift-ingress
# Service name the OpenShift gateway controller derives from <gateway>-<gatewayclass>
__rhoai_maas_gateway_service: "{{ __rhoai_maas_gateway_name }}-openshift-default"
# Component settings layered on top of Managed/Removed in the DataScienceCluster
__rhoai_maas_dsc_component_overrides:
aigateway:
modelsAsAService:
managementState: Managed
kserve:
rawDeploymentServiceConfig: Headed
nim:
managementState: Removed