Skip to content

Commit 17b939c

Browse files
authored
feat(helm): Add OpenShift Route support and improve deployment (#10)
1 parent 80b4702 commit 17b939c

13 files changed

+337
-39
lines changed

charts/qiskit-studio/README.md

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,49 @@ helm uninstall qiskit-studio-local
126126

127127
## Deployment Scenarios
128128

129+
### OpenShift Deployment with Routes
130+
131+
For OpenShift clusters, the chart supports native OpenShift Routes instead of Kubernetes Ingress. Routes provide automatic TLS termination and integrate with OpenShift's built-in router.
132+
133+
**Example: Deploying to OpenShift cluster**
134+
135+
```bash
136+
helm install qiskit-studio charts/qiskit-studio \
137+
-n qiskitstudio \
138+
-f charts/qiskit-studio/values-openshift-research.yaml \
139+
--disable-openapi-validation
140+
```
141+
142+
**Note:** The `--disable-openapi-validation` flag is required **only when using OpenShift Routes**. While Helm can validate OpenShift CRDs, validation often fails due to schema inconsistencies between the chart and OpenShift's API server (e.g., field type mismatches, stale cached schemas, or strict enforcement of deprecated fields). This flag skips Helm's client-side validation, allowing the manifests to be sent directly to OpenShift where the admission controller performs the authoritative validation. This flag is not needed for standard Kubernetes Ingress deployments.
143+
144+
This configuration:
145+
- Enables OpenShift Routes (`route.enabled: true`)
146+
- Disables Kubernetes Ingress
147+
- Creates routes with automatic hostname generation: `<service-name>.<namespace>.<domain>`
148+
- Configures edge TLS termination with automatic certificates
149+
- Sets appropriate resource limits for OpenShift LimitRange policies
150+
151+
**Accessing the application:**
152+
- Frontend: `https://frontend.qiskitstudio.example.com`
153+
- Chat API: `https://chat.qiskitstudio.example.com`
154+
- Codegen API: `https://codegen.qiskitstudio.example.com`
155+
- Coderun API: `https://coderun.qiskitstudio.example.com`
156+
157+
**Route Configuration:**
158+
159+
Routes are automatically generated using the pattern: `<service-name>.<namespace>.<domain>`
160+
161+
Default service names are: `frontend`, `chat`, `codegen`, `coderun`
162+
163+
To customize route names or hostnames for a specific service:
164+
165+
```yaml
166+
frontend:
167+
route:
168+
name: "studio" # Changes route to: studio.qiskitstudio.example.com
169+
host: "my-custom-hostname.example.com" # Or override hostname completely
170+
```
171+
129172
### Local Deployment (Default)
130173
131174
The Quick Start guide above uses the `values-local.yaml` file, which is the recommended method for local development. This configuration disables Ingress and exposes services via `NodePort` for easy access on your local machine.
@@ -209,7 +252,11 @@ This table lists the parameters that you are most likely to configure for both l
209252
| `codegen.image.tag` | The codegen image tag. | `0.5.0` |
210253
| `coderun.image.repository` | The coderun image repository. | `coderun-agent` |
211254
| `coderun.image.tag` | The coderun image tag. | `v0.0.3` |
212-
| `ingress.enabled` | Whether to enable ingress. | `false` |
255+
| `route.enabled` | Whether to enable OpenShift Routes. | `false` |
256+
| `route.type` | Route type (`openshift` for OpenShift Routes). | `"openshift"` |
257+
| `route.host` | Custom route hostname (leave empty for auto-generated). | `""` |
258+
| `route.timeout` | HAProxy timeout for routes. | `"120s"` |
259+
| `ingress.enabled` | Whether to enable Kubernetes Ingress. | `false` |
213260
| `ingress.host` | The ingress host. (Configured in `values-cloud.yaml`) | `agents.experimental.quantum.ibm.com` |
214261
| `extraEnvVars` | A list of additional environment variables to be added to all pods. | `[]` |
215262

charts/qiskit-studio/templates/_helpers.tpl

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,10 @@ Return the public-facing origin URL(s) for the frontend, including additional CO
9898
*/}}
9999
{{- define "qiskit-studio.frontend.origin" -}}
100100
{{- $origins := list -}}
101-
{{- if .Values.ingress.enabled -}}
101+
{{- if and .Values.route.enabled (eq .Values.route.type "openshift") -}}
102+
{{- $host := .Values.frontend.route.host | default (printf "%s.%s.%s" .Values.frontend.route.name .Release.Namespace .Values.route.domain) -}}
103+
{{- $origins = append $origins (printf "https://%s" $host) -}}
104+
{{- else if .Values.ingress.enabled -}}
102105
{{- $origins = append $origins (printf "%s://%s" .Values.global.scheme .Values.ingress.host) -}}
103106
{{- else -}}
104107
{{- $origins = append $origins (printf "%s://%s:%d" .Values.global.scheme .Values.global.hostname (int .Values.frontend.service.nodePort)) -}}
@@ -114,7 +117,10 @@ Return the public-facing origin URL(s) for the frontend, including additional CO
114117
Return the public-facing origin URL for the chat service.
115118
*/}}
116119
{{- define "qiskit-studio.chat.origin" -}}
117-
{{- if .Values.ingress.enabled -}}
120+
{{- if and .Values.route.enabled (eq .Values.route.type "openshift") -}}
121+
{{- $host := .Values.chat.route.host | default (printf "%s.%s.%s" .Values.chat.route.name .Release.Namespace .Values.route.domain) -}}
122+
{{- printf "https://%s" $host -}}
123+
{{- else if .Values.ingress.enabled -}}
118124
{{- printf "%s://%s/chat" .Values.global.scheme .Values.ingress.host -}}
119125
{{- else -}}
120126
{{- printf "%s://%s:%d" .Values.global.scheme .Values.global.hostname (int .Values.chat.service.nodePort) -}}
@@ -125,7 +131,10 @@ Return the public-facing origin URL for the chat service.
125131
Return the public-facing origin URL for the codegen service.
126132
*/}}
127133
{{- define "qiskit-studio.codegen.origin" -}}
128-
{{- if .Values.ingress.enabled -}}
134+
{{- if and .Values.route.enabled (eq .Values.route.type "openshift") -}}
135+
{{- $host := .Values.codegen.route.host | default (printf "%s.%s.%s" .Values.codegen.route.name .Release.Namespace .Values.route.domain) -}}
136+
{{- printf "https://%s" $host -}}
137+
{{- else if .Values.ingress.enabled -}}
129138
{{- printf "%s://%s/codegen" .Values.global.scheme .Values.ingress.host -}}
130139
{{- else -}}
131140
{{- printf "%s://%s:%d" .Values.global.scheme .Values.global.hostname (int .Values.codegen.service.nodePort) -}}
@@ -136,7 +145,10 @@ Return the public-facing origin URL for the codegen service.
136145
Return the public-facing origin URL for the coderun service.
137146
*/}}
138147
{{- define "qiskit-studio.coderun.origin" -}}
139-
{{- if .Values.ingress.enabled -}}
148+
{{- if and .Values.route.enabled (eq .Values.route.type "openshift") -}}
149+
{{- $host := .Values.coderun.route.host | default (printf "%s.%s.%s" .Values.coderun.route.name .Release.Namespace .Values.route.domain) -}}
150+
{{- printf "https://%s" $host -}}
151+
{{- else if .Values.ingress.enabled -}}
140152
{{- printf "%s://%s/coderun" .Values.global.scheme .Values.ingress.host -}}
141153
{{- else -}}
142154
{{- printf "%s://%s:%d" .Values.global.scheme .Values.global.hostname (int .Values.coderun.service.nodePort) -}}

charts/qiskit-studio/templates/chat-deployment.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ spec:
8585
initialDelaySeconds: 30
8686
periodSeconds: 10
8787
timeoutSeconds: 5
88+
resources:
89+
{{ toYaml .Values.chat.resources | indent 10 }}
8890
{{- $pullSecret := include "qiskit-studio.imagePullSecretName" . }}
8991
{{- if $pullSecret }}
9092
imagePullSecrets:

charts/qiskit-studio/templates/codegen-deployment.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ spec:
8383
initialDelaySeconds: 30
8484
periodSeconds: 10
8585
timeoutSeconds: 5
86+
resources:
87+
{{ toYaml .Values.codegen.resources | indent 10 }}
8688
{{- $pullSecret := include "qiskit-studio.imagePullSecretName" . }}
8789
{{- if $pullSecret }}
8890
imagePullSecrets:

charts/qiskit-studio/templates/coderun-deployment.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ spec:
2525
{{- include "qiskit-studio.extraEnvVars" (dict "Values" .Values "extraEnvVars" .Values.coderun.extraEnvVars) | nindent 8 }}
2626
ports:
2727
- containerPort: {{ .Values.coderun.service.port }}
28+
resources:
29+
{{ toYaml .Values.coderun.resources | indent 10 }}
2830
{{- $pullSecret := include "qiskit-studio.imagePullSecretName" . }}
2931
{{- if $pullSecret }}
3032
imagePullSecrets:

charts/qiskit-studio/templates/frontend-studio-deployment.yaml

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ spec:
1919
serviceAccountName: {{ .Values.frontend.serviceAccountName }}
2020
initContainers:
2121
- name: wait-for-chat
22-
image: busybox:1.37
23-
command: ['sh', '-c', 'until nc -z {{ .Release.Name }}-chat {{ .Values.chat.service.port }}; do echo waiting for chat service; sleep 2; done;']
22+
image: registry.access.redhat.com/ubi9/ubi-minimal:latest
23+
command: ['/bin/bash', '-c', 'until (echo > /dev/tcp/{{ .Release.Name }}-chat/{{ .Values.chat.service.port }}) >/dev/null 2>&1; do echo waiting for chat service; sleep 2; done;']
2424
- name: wait-for-codegen
25-
image: busybox:1.37
26-
command: ['sh', '-c', 'until nc -z {{ .Release.Name }}-codegen {{ .Values.codegen.service.port }}; do echo waiting for codegen service; sleep 2; done;']
25+
image: registry.access.redhat.com/ubi9/ubi-minimal:latest
26+
command: ['/bin/bash', '-c', 'until (echo > /dev/tcp/{{ .Release.Name }}-codegen/{{ .Values.codegen.service.port }}) >/dev/null 2>&1; do echo waiting for codegen service; sleep 2; done;']
2727
- name: wait-for-coderun
28-
image: busybox:1.37
29-
command: ['sh', '-c', 'until nc -z {{ .Release.Name }}-coderun {{ .Values.coderun.service.port }}; do echo waiting for coderun service; sleep 2; done;']
28+
image: registry.access.redhat.com/ubi9/ubi-minimal:latest
29+
command: ['/bin/bash', '-c', 'until (echo > /dev/tcp/{{ .Release.Name }}-coderun/{{ .Values.coderun.service.port }}) >/dev/null 2>&1; do echo waiting for coderun service; sleep 2; done;']
3030
containers:
3131
- name: frontend
3232
image: "{{ .Values.frontend.image.repository }}:{{ .Values.frontend.image.tag }}"
@@ -41,6 +41,17 @@ spec:
4141
value: "{{ include "qiskit-studio.codegen.origin" . }}/chat"
4242
- name: NEXT_PUBLIC_RUNCODE_URL
4343
value: "{{ include "qiskit-studio.coderun.origin" . }}/run"
44+
resources:
45+
{{ toYaml .Values.frontend.resources | indent 10 }}
46+
{{- if .Values.frontend.startupProbe }}
47+
startupProbe:
48+
tcpSocket:
49+
port: {{ .Values.frontend.service.port }}
50+
initialDelaySeconds: {{ .Values.frontend.startupProbe.initialDelaySeconds }}
51+
periodSeconds: {{ .Values.frontend.startupProbe.periodSeconds }}
52+
timeoutSeconds: {{ .Values.frontend.startupProbe.timeoutSeconds }}
53+
failureThreshold: {{ .Values.frontend.startupProbe.failureThreshold }}
54+
{{- end }}
4455
readinessProbe:
4556
tcpSocket:
4657
port: {{ .Values.frontend.service.port }}
@@ -52,21 +63,23 @@ spec:
5263
initialDelaySeconds: 30
5364
periodSeconds: 10
5465
- name: ui-warmup
55-
image: busybox:1.37
56-
command: ["sh", "-c"]
66+
image: registry.access.redhat.com/ubi9/ubi-minimal:latest
67+
command: ["/bin/bash", "-c"]
5768
args:
5869
- |
5970
echo "Waiting for frontend to be ready..."
60-
until wget -q -O /dev/null http://{{ .Release.Name }}-frontend:{{ .Values.frontend.service.port }}/; do
71+
until curl -f -s http://{{ .Release.Name }}-frontend:{{ .Values.frontend.service.port }}/ >/dev/null 2>&1; do
6172
echo "Frontend not yet ready, retrying in 5 seconds..."
6273
sleep 5
6374
done
6475
echo "Frontend is ready. Warming up UI..."
65-
wget -q -O /dev/null http://{{ .Release.Name }}-frontend:{{ .Values.frontend.service.port }}/
76+
curl -f -s http://{{ .Release.Name }}-frontend:{{ .Values.frontend.service.port }}/ >/dev/null 2>&1
6677
echo "UI warmed up."
6778
sleep infinity # Keep the container alive to prevent CrashLoopBackOff
6879
{{- $pullSecret := include "qiskit-studio.imagePullSecretName" . }}
6980
{{- if $pullSecret }}
7081
imagePullSecrets:
7182
- name: {{ $pullSecret }}
7283
{{- end }}
84+
85+
# Made with Bob

charts/qiskit-studio/templates/knowledge-mcp-deployment.yaml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,15 @@ spec:
6262
readOnly: true
6363
{{- end }}
6464
- name: wait-for-milvus
65-
image: busybox:1.37 # A small image with basic networking tools
66-
command: ['sh', '-c', 'until nc -z {{ .Release.Name }}-milvus-service {{ .Values.milvus.service.httpPort }}; do echo waiting for milvus; sleep 2; done;']
65+
image: registry.access.redhat.com/ubi9/ubi-minimal:latest
66+
command:
67+
- bash
68+
- -c
69+
- |
70+
until timeout 1 bash -c "cat < /dev/null > /dev/tcp/{{ .Release.Name }}-milvus-service/{{ .Values.milvus.service.httpPort }}"; do
71+
echo waiting for milvus
72+
sleep 2
73+
done
6774
volumes:
6875
- name: vllm-embedding-api-key-volume
6976
secret:

charts/qiskit-studio/templates/knowledge-milvus-deployment.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ spec:
5757
periodSeconds: 10
5858
timeoutSeconds: 5
5959
failureThreshold: 15
60+
resources:
61+
{{ toYaml .Values.milvus.resources | indent 10 }}
6062
volumeMounts:
6163
- name: milvus-data
6264
mountPath: /var/lib/milvus

charts/qiskit-studio/templates/knowledge-preloader-job.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ spec:
2121
serviceAccountName: {{ .Values.knowledgePreloaderJob.serviceAccountName }}
2222
initContainers:
2323
- name: wait-for-services
24-
image: busybox:1.37
24+
image: registry.access.redhat.com/ubi9/ubi-minimal:latest
2525
command:
26-
- "/bin/sh"
26+
- "/bin/bash"
2727
- "-c"
2828
- |
2929
echo "--- Waiting for {{ .Release.Name }}-knowledge-mcp-service to be ready ---"
30-
until nc -z {{ .Release.Name }}-knowledge-mcp-service {{ .Values.knowledgeMcp.service.port }};
30+
until (echo > /dev/tcp/{{ .Release.Name }}-knowledge-mcp-service/{{ .Values.knowledgeMcp.service.port }}) >/dev/null 2>&1;
3131
do
3232
echo "MCP service is not yet available. Retrying in 10 seconds..."
3333
sleep 10
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
{{- if and .Values.route.enabled (eq .Values.route.type "openshift") }}
2+
---
3+
apiVersion: route.openshift.io/v1
4+
kind: Route
5+
metadata:
6+
name: {{ .Release.Name }}-chat
7+
namespace: {{ .Release.Namespace }}
8+
labels:
9+
{{- include "qiskit-studio.labels" . | nindent 4 }}
10+
app.kubernetes.io/component: chat
11+
ingress: {{ .Release.Namespace }}
12+
annotations:
13+
haproxy.router.openshift.io/timeout: {{ .Values.route.timeout | default "120s" }}
14+
{{- with .Values.route.annotations }}
15+
{{- toYaml . | nindent 4 }}
16+
{{- end }}
17+
spec:
18+
host: {{ .Values.chat.route.host | default (printf "%s.%s.%s" .Values.chat.route.name .Release.Namespace .Values.route.domain) }}
19+
path: /
20+
port:
21+
targetPort: {{ .Values.chat.service.port }}
22+
tls:
23+
insecureEdgeTerminationPolicy: Redirect
24+
termination: edge
25+
to:
26+
kind: Service
27+
name: {{ .Release.Name }}-chat
28+
weight: 100
29+
wildcardPolicy: None
30+
---
31+
apiVersion: route.openshift.io/v1
32+
kind: Route
33+
metadata:
34+
name: {{ .Release.Name }}-codegen
35+
namespace: {{ .Release.Namespace }}
36+
labels:
37+
{{- include "qiskit-studio.labels" . | nindent 4 }}
38+
app.kubernetes.io/component: codegen
39+
ingress: {{ .Release.Namespace }}
40+
annotations:
41+
haproxy.router.openshift.io/timeout: {{ .Values.route.timeout | default "120s" }}
42+
{{- with .Values.route.annotations }}
43+
{{- toYaml . | nindent 4 }}
44+
{{- end }}
45+
spec:
46+
host: {{ .Values.codegen.route.host | default (printf "%s.%s.%s" .Values.codegen.route.name .Release.Namespace .Values.route.domain) }}
47+
path: /
48+
port:
49+
targetPort: {{ .Values.codegen.service.port }}
50+
tls:
51+
insecureEdgeTerminationPolicy: Redirect
52+
termination: edge
53+
to:
54+
kind: Service
55+
name: {{ .Release.Name }}-codegen
56+
weight: 100
57+
wildcardPolicy: None
58+
---
59+
apiVersion: route.openshift.io/v1
60+
kind: Route
61+
metadata:
62+
name: {{ .Release.Name }}-coderun
63+
namespace: {{ .Release.Namespace }}
64+
labels:
65+
{{- include "qiskit-studio.labels" . | nindent 4 }}
66+
app.kubernetes.io/component: coderun
67+
ingress: {{ .Release.Namespace }}
68+
annotations:
69+
haproxy.router.openshift.io/timeout: {{ .Values.route.timeout | default "120s" }}
70+
{{- with .Values.route.annotations }}
71+
{{- toYaml . | nindent 4 }}
72+
{{- end }}
73+
spec:
74+
host: {{ .Values.coderun.route.host | default (printf "%s.%s.%s" .Values.coderun.route.name .Release.Namespace .Values.route.domain) }}
75+
path: /
76+
port:
77+
targetPort: {{ .Values.coderun.service.port }}
78+
tls:
79+
insecureEdgeTerminationPolicy: Redirect
80+
termination: edge
81+
to:
82+
kind: Service
83+
name: {{ .Release.Name }}-coderun
84+
weight: 100
85+
wildcardPolicy: None
86+
{{- end }}
87+
88+
# Made with Bob

0 commit comments

Comments
 (0)