From fb0572eba16c69e8e2dfc22652edb97d864a4b26 Mon Sep 17 00:00:00 2001 From: Ani Sinanaj Date: Sun, 17 May 2026 15:48:04 +0200 Subject: [PATCH] Model Imogen as a shared Olares service The Olares package now follows the shared-app pattern used by official API and ComfyUI examples: one admin-owned backend chart is marked shared, while each member gets a small user-space proxy entrance for the normal Olares endpoint. The manifest exposes a hidden sharedEntrance for app-to-app calls and keeps the user-facing API route for browser or user-scoped access. Constraint: Olares shared applications require the cluster backend and user access surface to be modeled separately. Constraint: Helm is not installed in this environment, so validation used Ruby YAML parsing and kubectl client dry-runs instead of helm template/package. Rejected: Keep the backend in the root chart | that would continue installing the GPU API per user instead of once as a shared service. Confidence: high Scope-risk: narrow Directive: Keep imagegenapiserver as the only chart that deploys the GPU backend; user installs should only proxy to imagegenapiserver-shared. Tested: Ruby YAML parsing for chart values and admin/member OlaresManifest render paths; kubectl create --dry-run=client for rendered user proxy and shared backend resources; git diff --check. Not-tested: helm lint/template/package because helm is unavailable locally; live Olares install. --- README.md | 12 +- olares/imagegenapi/Chart.yaml | 4 +- olares/imagegenapi/OlaresManifest.yaml | 77 ++++++++--- olares/imagegenapi/README.md | 28 ++-- olares/imagegenapi/imagegenapi/Chart.yaml | 6 + .../imagegenapi/templates/clientproxy.yaml | 122 ++++++++++++++++++ olares/imagegenapi/imagegenapi/values.yaml | 5 + .../imagegenapi/imagegenapiserver/Chart.yaml | 6 + .../templates/configmap.yaml | 9 +- .../templates/deployment.yaml | 19 ++- .../imagegenapi/imagegenapiserver/values.yaml | 20 +++ 11 files changed, 277 insertions(+), 31 deletions(-) create mode 100644 olares/imagegenapi/imagegenapi/Chart.yaml create mode 100644 olares/imagegenapi/imagegenapi/templates/clientproxy.yaml create mode 100644 olares/imagegenapi/imagegenapi/values.yaml create mode 100644 olares/imagegenapi/imagegenapiserver/Chart.yaml rename olares/imagegenapi/{ => imagegenapiserver}/templates/configmap.yaml (70%) rename olares/imagegenapi/{ => imagegenapiserver}/templates/deployment.yaml (91%) create mode 100644 olares/imagegenapi/imagegenapiserver/values.yaml diff --git a/README.md b/README.md index 2fec49b..4005111 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Image Generation API +# Imogen Image Generation API FastAPI wrapper that exposes OpenAI-style image generation endpoints backed by Diffusers. @@ -24,6 +24,15 @@ curl -X POST http://localhost:8001/v1/images/generations \ }' ``` +## Olares Packaging + +The Olares chart packages Imogen as a shared application: + +- Admin install: one shared GPU-backed backend for the cluster. +- User install: a lightweight user-space API entrance that proxies to the shared backend. +- Shared endpoint: `http://imagegenapi.shared.olares.com` +- User endpoint: `https://imagegenapi.{OlaresID}.olares.com` + ## Environment Variables - `HOST` (default: `0.0.0.0`) @@ -38,4 +47,3 @@ curl -X POST http://localhost:8001/v1/images/generations \ - `IMAGE_MAX_HEIGHT` (default: `1536`) - `IMAGE_OUTPUT_DIR` (default: `/data/outputs`) - `IMAGE_OUTPUT_FORMAT` (`png|jpeg|jpg`, default: `png`) - diff --git a/olares/imagegenapi/Chart.yaml b/olares/imagegenapi/Chart.yaml index 120b9eb..a4da2d0 100644 --- a/olares/imagegenapi/Chart.yaml +++ b/olares/imagegenapi/Chart.yaml @@ -1,6 +1,6 @@ apiVersion: v2 name: imagegenapi -description: Olares app for OpenAI-style image generation API +description: Olares shared app for Imogen OpenAI-style image generation API type: application version: 1.2.0 -appVersion: "1.2.0" \ No newline at end of file +appVersion: "1.2.0" diff --git a/olares/imagegenapi/OlaresManifest.yaml b/olares/imagegenapi/OlaresManifest.yaml index 41946cd..1a86a2b 100644 --- a/olares/imagegenapi/OlaresManifest.yaml +++ b/olares/imagegenapi/OlaresManifest.yaml @@ -1,25 +1,46 @@ olaresManifest.version: "0.11.0" olaresManifest.type: app +apiVersion: "v2" metadata: name: imagegenapi - description: Private image generation API powered by Stable Diffusion XL. + description: Shared Imogen image generation API powered by Stable Diffusion XL. icon: https://avatars.githubusercontent.com/u/139895814?s=200&v=4 appid: imagegenapi - title: Image Generation API + title: Imogen Image Generation API version: "1.2.0" categories: - AI - Utilities - Developer Tools +sharedEntrances: + - name: imagegenapi + title: Imogen API + host: sharedentrances-imogen + icon: https://avatars.githubusercontent.com/u/139895814?s=200&v=4 + port: 0 + invisible: true + authLevel: internal +entrances: + - name: imagegenapi + port: 8080 + host: imagegenapi-web-svc + title: Imogen API + icon: https://avatars.githubusercontent.com/u/139895814?s=200&v=4 + authLevel: internal + openMethod: window permission: appData: true appCache: true +{{- if and .Values.admin .Values.bfl.username (eq .Values.admin .Values.bfl.username) }} userData: - Home +{{- end }} spec: versionName: "gpu-cu128" fullDescription: | - Image Generation API exposes OpenAI-style image generation endpoints on Olares. + Imogen Image Generation API exposes OpenAI-style image generation endpoints on Olares. + + This is the shared Olares package. The administrator installs one shared Imogen backend for the cluster, while each user installs a lightweight user-space API entrance that proxies to that shared backend. Endpoints - `GET /` @@ -42,13 +63,17 @@ spec: - `GET /v1/images/{image_id}` - Serves generated images when `response_format=url`. + Olares endpoints + - Shared app-to-app endpoint: `http://imagegenapi.shared.olares.com` + - User-space endpoint: `https://imagegenapi.{OlaresID}.olares.com` + Example request - - `curl -X POST http://imagegenapi-svc:8001/v1/images/generations -H "Content-Type: application/json" -d '{"model":"stabilityai/stable-diffusion-xl-base-1.0","prompt":"a cinematic mountain landscape at sunrise","size":"1024x1024","response_format":"url"}'` + - `curl -X POST http://imagegenapi.shared.olares.com/v1/images/generations -H "Content-Type: application/json" -d '{"model":"stabilityai/stable-diffusion-xl-base-1.0","prompt":"a cinematic mountain landscape at sunrise","size":"1024x1024","response_format":"url"}'` Notes - First request may be slow while model weights are pulled into app data. - - Hugging Face and torch caches are persisted under `userspace.appData`. - - Generated image files are persisted under `userspace.appData/outputs`. + - Hugging Face and torch caches are persisted by the shared backend installation. + - Generated image files are persisted by the shared backend under `outputs`. - This package targets `amd64` Olares nodes with NVIDIA GPU support. developer: progress44 website: https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0 @@ -60,6 +85,7 @@ spec: url: https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0 locale: - en-US +{{- if and .Values.admin .Values.bfl.username (eq .Values.admin .Values.bfl.username) }} requiredMemory: 12Gi limitedMemory: 12Gi requiredDisk: 5Gi @@ -68,14 +94,42 @@ spec: limitedCpu: 4 requiredGpu: 12Gi limitedGpu: 16Gi +{{- else }} + requiredMemory: 64Mi + limitedMemory: 256Mi + requiredDisk: 1Mi + limitedDisk: 100Mi + requiredCpu: 10m + limitedCpu: 100m +{{- end }} supportArch: - amd64 + subCharts: + - name: imagegenapiserver + shared: true + - name: imagegenapi options: apiTimeout: 0 dependencies: - name: olares type: system - version: ">=1.12.1-0" + version: ">=1.12.3-0" +{{- if and .Values.admin .Values.bfl.username (eq .Values.admin .Values.bfl.username) }} +{{- else }} + - name: imagegenapi + type: application + version: ">=1.2.0" + mandatory: true +{{- end }} + appScope: +{{- if and .Values.admin .Values.bfl.username (eq .Values.admin .Values.bfl.username) }} + clusterScoped: true + appRef: + - imagegenapi +{{- else }} + clusterScoped: false +{{- end }} +{{- if and .Values.admin .Values.bfl.username (eq .Values.admin .Values.bfl.username) }} envs: - envName: OLARES_USER_HUGGINGFACE_TOKEN required: false @@ -87,11 +141,4 @@ envs: applyOnChange: true valueFrom: envName: OLARES_USER_HUGGINGFACE_SERVICE -entrances: - - name: imagegenapi - port: 8001 - host: imagegenapi-svc - title: Image Generation API - icon: https://avatars.githubusercontent.com/u/139895814?s=200&v=4 - authLevel: internal - openMethod: window +{{- end }} diff --git a/olares/imagegenapi/README.md b/olares/imagegenapi/README.md index 7ac7029..0c1f297 100644 --- a/olares/imagegenapi/README.md +++ b/olares/imagegenapi/README.md @@ -1,12 +1,24 @@ -# Image Generation API for Olares +# Imogen Image Generation API for Olares -This package deploys the published image: +This package deploys Imogen as an Olares shared application using the published image: - `ghcr.io/progress44/rpi-system-image-gen-api:latest` -The app exposes OpenAI-style image generation endpoints at: +The administrator installs one shared GPU-backed backend for the Olares cluster. +Each user gets a lightweight user-space API entrance that proxies to that shared backend. -- `http://imagegenapi-svc:8001` +## Olares Endpoints + +- Shared app-to-app endpoint: `http://imagegenapi.shared.olares.com` +- User-space endpoint: `https://imagegenapi.{OlaresID}.olares.com` + +Use the shared endpoint for backend-to-backend calls from other Olares apps. Use the +user-space endpoint when a browser or user-installed app needs the normal Olares route. + +## Chart Structure + +- `imagegenapiserver`: shared backend chart deployed once for the cluster by the admin. +- `imagegenapi`: per-user OpenResty proxy chart that exposes the normal user-space API entrance. ## Endpoints @@ -19,7 +31,7 @@ The app exposes OpenAI-style image generation endpoints at: ## Request example ```bash -curl -X POST http://imagegenapi-svc:8001/v1/images/generations \ +curl -X POST http://imagegenapi.shared.olares.com/v1/images/generations \ -H "Content-Type: application/json" \ -d '{ "model": "stabilityai/stable-diffusion-xl-base-1.0", @@ -32,7 +44,7 @@ curl -X POST http://imagegenapi-svc:8001/v1/images/generations \ ## Notes - The first request may be slower while model files are downloaded. -- Hugging Face and torch caches persist under `userspace.appData`. -- Generated outputs persist under `userspace.appData/outputs`. +- Hugging Face and torch caches persist with the shared backend installation. +- Generated outputs persist with the shared backend under `outputs`. - Use Olares env variables `OLARES_USER_HUGGINGFACE_TOKEN` and - `OLARES_USER_HUGGINGFACE_SERVICE` if needed. + `OLARES_USER_HUGGINGFACE_SERVICE` on the admin install if needed. diff --git a/olares/imagegenapi/imagegenapi/Chart.yaml b/olares/imagegenapi/imagegenapi/Chart.yaml new file mode 100644 index 0000000..835b4c1 --- /dev/null +++ b/olares/imagegenapi/imagegenapi/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: imagegenapi +description: User-space API entrance for shared Imogen image generation API +type: application +version: 1.2.0 +appVersion: "1.25.3-2" diff --git a/olares/imagegenapi/imagegenapi/templates/clientproxy.yaml b/olares/imagegenapi/imagegenapi/templates/clientproxy.yaml new file mode 100644 index 0000000..b77d3d9 --- /dev/null +++ b/olares/imagegenapi/imagegenapi/templates/clientproxy.yaml @@ -0,0 +1,122 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: imagegenapi-nginx-config + namespace: {{ .Release.Namespace }} +data: + nginx.conf: | + server { + listen {{ .Values.proxy.port }}; + access_log /opt/bitnami/openresty/nginx/logs/access.log; + error_log /opt/bitnami/openresty/nginx/logs/error.log; + + proxy_connect_timeout 60s; + proxy_send_timeout 900s; + proxy_read_timeout 900s; + proxy_http_version 1.1; + + proxy_set_header Host $host; + proxy_set_header X-Forwarded-Host $http_host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Authorization $http_authorization; + proxy_set_header Cookie $http_cookie; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + location / { + resolver coredns.kube-system.svc.cluster.local valid=10s; + proxy_pass http://{{ .Values.proxy.backendHost }}:{{ .Values.proxy.backendPort }}; + proxy_next_upstream error timeout http_500 http_502 http_503 http_504; + + proxy_hide_header Access-Control-Allow-Origin; + proxy_hide_header Access-Control-Allow-Methods; + proxy_hide_header Access-Control-Allow-Headers; + add_header Access-Control-Allow-Origin * always; + add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS, HEAD" always; + add_header Access-Control-Allow-Headers "authorization,content-type,x-csrftoken,deviceType,token" always; + + if ($request_method = 'OPTIONS') { + return 204; + } + } + } +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }} + namespace: {{ .Release.Namespace }} + labels: + app: imagegenapi-web +spec: + replicas: 1 + selector: + matchLabels: + app: imagegenapi-web + strategy: + type: Recreate + template: + metadata: + labels: + app: imagegenapi-web + spec: + volumes: + - name: imagegenapi-nginx-config + configMap: + name: imagegenapi-nginx-config + items: + - key: nginx.conf + path: nginx.conf + containers: + - name: nginx + image: {{ .Values.proxy.image | quote }} + ports: + - containerPort: {{ .Values.proxy.port }} + protocol: TCP + env: + - name: OPENRESTY_CONF_FILE + value: /etc/nginx/nginx.conf + readinessProbe: + exec: + command: + - /bin/sh + - -c + - | + http_code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:{{ .Values.proxy.port }}/health) + [ $http_code -ge 200 ] && [ $http_code -lt 500 ] + initialDelaySeconds: 2 + timeoutSeconds: 3 + periodSeconds: 3 + successThreshold: 1 + failureThreshold: 60 + resources: + limits: + cpu: 100m + memory: 256Mi + requests: + cpu: 10m + memory: 64Mi + volumeMounts: + - name: imagegenapi-nginx-config + mountPath: /etc/nginx/nginx.conf + subPath: nginx.conf + - name: imagegenapi-nginx-config + mountPath: /opt/bitnami/openresty/nginx/conf/server_blocks/nginx.conf + subPath: nginx.conf +--- +apiVersion: v1 +kind: Service +metadata: + name: imagegenapi-web-svc + namespace: {{ .Release.Namespace }} +spec: + type: ClusterIP + selector: + app: imagegenapi-web + ports: + - name: http + protocol: TCP + port: {{ .Values.proxy.port }} + targetPort: {{ .Values.proxy.port }} diff --git a/olares/imagegenapi/imagegenapi/values.yaml b/olares/imagegenapi/imagegenapi/values.yaml new file mode 100644 index 0000000..5ab3a1b --- /dev/null +++ b/olares/imagegenapi/imagegenapi/values.yaml @@ -0,0 +1,5 @@ +proxy: + image: docker.io/beclab/aboveos-bitnami-openresty:1.25.3-2 + backendHost: imagegenapi-svc.imagegenapiserver-shared + backendPort: 8001 + port: 8080 diff --git a/olares/imagegenapi/imagegenapiserver/Chart.yaml b/olares/imagegenapi/imagegenapiserver/Chart.yaml new file mode 100644 index 0000000..861cad8 --- /dev/null +++ b/olares/imagegenapi/imagegenapiserver/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: imagegenapiserver +description: Shared backend for Imogen image generation API +type: application +version: 1.2.0 +appVersion: "1.2.0" diff --git a/olares/imagegenapi/templates/configmap.yaml b/olares/imagegenapi/imagegenapiserver/templates/configmap.yaml similarity index 70% rename from olares/imagegenapi/templates/configmap.yaml rename to olares/imagegenapi/imagegenapiserver/templates/configmap.yaml index 6213f15..d256d05 100644 --- a/olares/imagegenapi/templates/configmap.yaml +++ b/olares/imagegenapi/imagegenapiserver/templates/configmap.yaml @@ -1,3 +1,5 @@ +{{- if and .Values.admin .Values.bfl.username (eq .Values.admin .Values.bfl.username) }} +{{- $olaresEnv := .Values.olaresEnv | default dict }} --- apiVersion: v1 kind: ConfigMap @@ -18,11 +20,12 @@ data: IMAGE_OUTPUT_FORMAT: {{ .Values.imagegen.outputFormat | quote }} IMAGE_OUTPUT_DIR: /data/outputs HF_HOME: /data/huggingface - HF_ENDPOINT: {{ .Values.olaresEnv.OLARES_USER_HUGGINGFACE_SERVICE | default "https://huggingface.co/" | quote }} - HF_TOKEN: {{ .Values.olaresEnv.OLARES_USER_HUGGINGFACE_TOKEN | default "" | quote }} - HUGGING_FACE_HUB_TOKEN: {{ .Values.olaresEnv.OLARES_USER_HUGGINGFACE_TOKEN | default "" | quote }} + HF_ENDPOINT: {{ get $olaresEnv "OLARES_USER_HUGGINGFACE_SERVICE" | default "https://huggingface.co/" | quote }} + HF_TOKEN: {{ get $olaresEnv "OLARES_USER_HUGGINGFACE_TOKEN" | default "" | quote }} + HUGGING_FACE_HUB_TOKEN: {{ get $olaresEnv "OLARES_USER_HUGGINGFACE_TOKEN" | default "" | quote }} HF_HUB_CACHE: /data/huggingface/hub TRANSFORMERS_CACHE: /data/huggingface/transformers TORCH_HOME: /data/torch NVIDIA_VISIBLE_DEVICES: {{ .Values.imagegen.nvidiaVisibleDevices | quote }} NVIDIA_DRIVER_CAPABILITIES: {{ .Values.imagegen.nvidiaDriverCapabilities | quote }} +{{- end }} diff --git a/olares/imagegenapi/templates/deployment.yaml b/olares/imagegenapi/imagegenapiserver/templates/deployment.yaml similarity index 91% rename from olares/imagegenapi/templates/deployment.yaml rename to olares/imagegenapi/imagegenapiserver/templates/deployment.yaml index 5ea058f..3007f50 100644 --- a/olares/imagegenapi/templates/deployment.yaml +++ b/olares/imagegenapi/imagegenapiserver/templates/deployment.yaml @@ -1,8 +1,9 @@ +{{- if and .Values.admin .Values.bfl.username (eq .Values.admin .Values.bfl.username) }} --- apiVersion: apps/v1 kind: Deployment metadata: - name: {{ .Release.Name }} + name: imagegenapi namespace: {{ .Release.Namespace }} labels: app: imagegenapi @@ -125,3 +126,19 @@ spec: protocol: TCP port: {{ .Values.service.port }} targetPort: http +--- +apiVersion: v1 +kind: Service +metadata: + name: sharedentrances-imogen + namespace: {{ .Release.Namespace }} +spec: + type: ClusterIP + selector: + app: imagegenapi + ports: + - name: http + protocol: TCP + port: 80 + targetPort: http +{{- end }} diff --git a/olares/imagegenapi/imagegenapiserver/values.yaml b/olares/imagegenapi/imagegenapiserver/values.yaml new file mode 100644 index 0000000..348bd83 --- /dev/null +++ b/olares/imagegenapi/imagegenapiserver/values.yaml @@ -0,0 +1,20 @@ +image: + repository: ghcr.io/progress44/rpi-system-image-gen-api + tag: latest + pullPolicy: Always + +service: + port: 8001 + +imagegen: + modelId: stabilityai/stable-diffusion-xl-base-1.0 + device: cuda + torchDtype: auto + defaultSteps: "30" + maxSteps: "80" + maxWidth: "1536" + maxHeight: "1536" + enableDocs: "true" + outputFormat: png + nvidiaVisibleDevices: all + nvidiaDriverCapabilities: compute,utility