mirror of
https://github.com/nexu-io/open-design.git
synced 2026-05-31 19:04:39 +07:00
MVP Helm chart (#2140)
Some checks failed
visual-baseline / Capture visual baselines (push) Waiting to run
actionlint / Lint GitHub Actions workflows (push) Failing after 1s
ci / Detect CI change scopes (push) Successful in 0s
nix-check / build (push) Failing after 1s
ci / Validate Nix flake (push) Has been skipped
ci / Preflight (push) Failing after 1s
ci / Workspace unit tests (push) Failing after 1s
ci / Daemon workspace tests (push) Failing after 1s
ci / Web workspace tests (push) Failing after 1s
ci / Browser tests (push) Failing after 1s
ci / Build workspaces (push) Failing after 1s
ci / Validate workspace (push) Failing after 0s
ci / Runtime trace (push) Has been skipped
Some checks failed
visual-baseline / Capture visual baselines (push) Waiting to run
actionlint / Lint GitHub Actions workflows (push) Failing after 1s
ci / Detect CI change scopes (push) Successful in 0s
nix-check / build (push) Failing after 1s
ci / Validate Nix flake (push) Has been skipped
ci / Preflight (push) Failing after 1s
ci / Workspace unit tests (push) Failing after 1s
ci / Daemon workspace tests (push) Failing after 1s
ci / Web workspace tests (push) Failing after 1s
ci / Browser tests (push) Failing after 1s
ci / Build workspaces (push) Failing after 1s
ci / Validate workspace (push) Failing after 0s
ci / Runtime trace (push) Has been skipped
* introduce MVP Helm chart * address review feedback and add config checksums * implement zero-trust sidecar proxy and fix health probes * address PR feedback on publicBaseUrl and sidecar * pass ingress headers and consolidate proxy port * fallback allowed origins for localhost when ingress is disabled to prevent CORS errors during port-forwarding * enforce proxy security and auto-derive base URL * hardcode service targetPort to proxy * require explicit base url for multi-host ingress * prevent silent CORS failures for LoadBalancer and NodePort and README updates * Added to the deployment pod annotations. This ensures that changes made to during a helm upgrade will properly trigger a rolling update of the pods instead of serving stale proxy configurations * dynamically derive scheme per host in allowed origins * dynamically derive scheme for single-host publicBaseUrl * add default apiToken placeholder and wrap ingress path guardrail * add recent changes to readme * explicitly disallow non-root ingress path prefixes * update README to reflect path and origin guardrails --------- Co-authored-by: shaarron <sharon.kroch@gmail.com>
This commit is contained in:
parent
b22c7713db
commit
6244b67295
13 changed files with 751 additions and 0 deletions
23
charts/open-design/.helmignore
Normal file
23
charts/open-design/.helmignore
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
.vscode/
|
||||
6
charts/open-design/Chart.yaml
Normal file
6
charts/open-design/Chart.yaml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: v2
|
||||
name: open-design
|
||||
description: A Helm chart for deploying Open Design on Kubernetes
|
||||
type: application
|
||||
version: 0.1.0
|
||||
appVersion: "0.7.0"
|
||||
165
charts/open-design/README.md
Normal file
165
charts/open-design/README.md
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
<!--- app-name: Open Design -->
|
||||
|
||||
## Introduction
|
||||
This chart bootstraps an [Open Design](https://github.com/nexu-io/open-design) deployment on a [Kubernetes](https://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager.
|
||||
|
||||
## Prerequisites
|
||||
- Kubernetes 1.23+
|
||||
- Helm 3.8.0+
|
||||
- PV provisioner support in the underlying infrastructure (if persistence is enabled)
|
||||
|
||||
## Installing the Chart
|
||||
To install the chart with the release name `my-release`:
|
||||
|
||||
```console
|
||||
helm install my-release ./charts/open-design
|
||||
```
|
||||
|
||||
These commands deploy an Open Design application on the Kubernetes cluster in the default configuration.
|
||||
|
||||
> **Tip**: List all releases using `helm list`
|
||||
|
||||
### Architecture and Configuration Notes
|
||||
|
||||
#### SQLite State & Concurrency Limitations
|
||||
The current Open Design runtime stores state in local files and SQLite under `/app/.od`. Because SQLite does not support concurrent writes from multiple network replicas, **this chart is strictly limited to 1 replica**.
|
||||
|
||||
Horizontal Pod Autoscaling (HPA) is disabled by default. Do not enable HPA or scale the deployment beyond `replicas: 1` unless you have modified the application to externalize the state to a standalone database.
|
||||
|
||||
#### Server-Sent Events (SSE) and Ingress
|
||||
Open Design relies on Server-Sent Events (SSE) for real-time streaming. If you enable the Ingress resource, it is critical to disable reverse-proxy buffering. If you are using the NGINX Ingress Controller, this chart automatically applies the required annotations by default:
|
||||
|
||||
```yaml
|
||||
nginx.ingress.kubernetes.io/proxy-buffering: "off"
|
||||
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
|
||||
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
|
||||
```
|
||||
|
||||
**Path Constraints**: Non-root ingress path prefixes (sub-paths) are explicitly **unsupported** by the proxy routing stack. Ingress paths must be configured as `/`.
|
||||
|
||||
#### Authentication Proxy
|
||||
An authentication proxy (NGINX) is introduced to front the application. This proxy runs as a mandatory sidecar container alongside the main application. The Kubernetes Service routes traffic to the proxy, which handles authentication for the API and health checks, proxying valid requests to the application.
|
||||
|
||||
#### Security Context
|
||||
This chart adheres to strict security defaults:
|
||||
- Runs as non-root user `1001`.
|
||||
- Drops all kernel capabilities (`ALL`).
|
||||
- Enforces a `readOnlyRootFilesystem`.
|
||||
- Prevents privilege escalation.
|
||||
|
||||
## Parameters
|
||||
|
||||
### Global & Image Parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
| ------------------ | ----------------------------------------- | ---------------------------- |
|
||||
| `commonLabels` | Custom labels injected into all resources | `{app.kubernetes.io/environment: production}` |
|
||||
| `image.repository` | Open Design image repository | `vanjayak/open-design` |
|
||||
| `image.pullPolicy` | Image pull policy | `IfNotPresent` |
|
||||
| `image.tag` | Image tag (overrides AppVersion) | `latest` |
|
||||
|
||||
### Application Configuration
|
||||
|
||||
| Name | Description | Value |
|
||||
| ---------------------- | ------------------------------------------------------------------------------------------------------------- | ------------------------------------ |
|
||||
| `config.nodeEnv` | Node.js environment (`production` or `development`) | `production` |
|
||||
| `config.allowedOrigins`| CORS allowed origins. Mandatory if service.type is LoadBalancer or NodePort to prevent 403 render failures. | `""` |
|
||||
| `config.publicBaseUrl` | Public base URL used by the application (derived dynamically if empty) | `""` |
|
||||
| `config.nodeOptions` | V8 engine memory optimizations | `--max-old-space-size=192` |
|
||||
| `config.webPort` | Web server listening port | `7456` |
|
||||
| `config.bindHost` | Host to bind the web server to | `"127.0.0.1"` |
|
||||
| `config.apiToken` | API authentication token (must be changed from default) | `"secure-default-token-change-me"` |
|
||||
|
||||
### Auth Proxy Parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
| -------------------------------------- | ------------------------------------------------- | ------------------------------------------------ |
|
||||
| `authProxy.image` | NGINX proxy image | `nginxinc/nginx-unprivileged:1.25-alpine-slim` |
|
||||
| `authProxy.port` | Proxy server port inside the container | `8080` |
|
||||
| `authProxy.securityContext` | Security context for the proxy container | `{...}` |
|
||||
|
||||
### Health Check Parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
| ------------------------------------------- | -------------------------------------------------- | ------ |
|
||||
| `livenessProbe.enabled` | Enable liveness probe | `true` |
|
||||
| `livenessProbe.initialDelaySeconds` | Initial delay seconds for liveness probe | `20` |
|
||||
| `livenessProbe.periodSeconds` | Period seconds for liveness probe | `30` |
|
||||
| `livenessProbe.timeoutSeconds` | Timeout seconds for liveness probe | `5` |
|
||||
| `livenessProbe.failureThreshold` | Failure threshold for liveness probe | `3` |
|
||||
| `readinessProbe.enabled` | Enable readiness probe | `true` |
|
||||
| `readinessProbe.initialDelaySeconds` | Initial delay seconds for readiness probe | `5` |
|
||||
| `readinessProbe.periodSeconds` | Period seconds for readiness probe | `10` |
|
||||
| `readinessProbe.timeoutSeconds` | Timeout seconds for readiness probe | `5` |
|
||||
| `readinessProbe.failureThreshold` | Failure threshold for readiness probe | `3` |
|
||||
|
||||
### Network & Ingress Parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
| --------------------------------------- | ------------------------------------------------------------ | -------------------------- |
|
||||
| `service.type` | Kubernetes Service type | `ClusterIP` |
|
||||
| `service.port` | Service HTTP port | `80` |
|
||||
| `ingress.enabled` | Enable ingress record generation | `false` |
|
||||
| `ingress.className` | Ingress class name | `nginx` |
|
||||
| `ingress.annotations` | Additional custom annotations for Ingress (e.g., SSE fixes) | `{...}` |
|
||||
| `ingress.hosts[0].host` | Hostname for the ingress record | `open-design.local` |
|
||||
| `ingress.tls` | TLS configuration for ingress records | `[]` |
|
||||
|
||||
### Persistence Parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
| -------------------------- | ------------------------------------------------------------ | --------------- |
|
||||
| `persistence.enabled` | Enable PVC for SQLite and file state | `true` |
|
||||
| `persistence.storageClass` | Storage class (leave empty to use cluster default) | `""` |
|
||||
| `persistence.accessMode` | PVC Access Mode | `ReadWriteOnce` |
|
||||
| `persistence.size` | PVC Storage Request | `10Gi` |
|
||||
|
||||
### Resources & Autoscaling Parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
| ----------------------------------- | --------------------------------------------------------------- | --------- |
|
||||
| `replicaCount` | Number of application replicas | `1` |
|
||||
| `resources.limits.cpu` | CPU limits for the container | `1000m` |
|
||||
| `resources.limits.memory` | Memory limits for the container | `1024Mi` |
|
||||
| `resources.requests.cpu` | CPU requests for the container | `200m` |
|
||||
| `resources.requests.memory` | Memory requests for the container | `256Mi` |
|
||||
| `hpa.enabled` | Enable Horizontal Pod Autoscaler (WARNING: Breaks SQLite) | `false` |
|
||||
|
||||
### Security & Scheduling Parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
| ----------------------------------------------------------------- | ----------------------------------------------- | ------------------ |
|
||||
| `podSecurityContext.fsGroupChangePolicy` | Set filesystem group change policy | `Always` |
|
||||
| `podSecurityContext.sysctls` | Set kernel settings using the sysctl interface | `[]` |
|
||||
| `podSecurityContext.supplementalGroups` | Set filesystem extra groups | `[]` |
|
||||
| `podSecurityContext.fsGroup` | Group ID for the persistent volume | `1001` |
|
||||
| `containerSecurityContext.seLinuxOptions` | Set SELinux options in container | `{}` |
|
||||
| `containerSecurityContext.runAsUser` | Run the application as this UID | `1001` |
|
||||
| `containerSecurityContext.runAsGroup` | Run the application as this GID | `1001` |
|
||||
| `containerSecurityContext.runAsNonRoot` | Set container's Security Context runAsNonRoot | `true` |
|
||||
| `containerSecurityContext.privileged` | Set container's Security Context privileged | `false` |
|
||||
| `containerSecurityContext.readOnlyRootFilesystem` | Enforce read-only root FS | `true` |
|
||||
| `containerSecurityContext.allowPrivilegeEscalation` | Set container's Security Context allowPrivilegeEscalation | `false` |
|
||||
| `containerSecurityContext.capabilities.drop` | List of capabilities to be dropped | `["ALL"]` |
|
||||
| `containerSecurityContext.seccompProfile.type` | Set container's Security Context seccomp profile| `"RuntimeDefault"` |
|
||||
| `nodeSelector` | Node labels for pod assignment | `{}` |
|
||||
| `tolerations` | Tolerations for pod assignment | `[]` |
|
||||
| `affinity` | Affinity rules for pod assignment | `{}` |
|
||||
| `initContainers` | Additional init containers to add to the pod | `[]` |
|
||||
| `sidecars` | Additional sidecar containers to add to the pod | `[]` |
|
||||
|
||||
Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example,
|
||||
|
||||
```console
|
||||
helm install my-release --set config.nodeEnv=development ./charts/open-design
|
||||
```
|
||||
|
||||
The above command sets the Open Design node environment to `development`.
|
||||
|
||||
Alternatively, a YAML file that specifies the values for the parameters can be provided while installing the chart. For example,
|
||||
|
||||
```console
|
||||
helm install my-release -f values.yaml ./charts/open-design
|
||||
```
|
||||
|
||||
> **Tip**: You can use the default [values.yaml](values.yaml)
|
||||
52
charts/open-design/templates/_helpers.tpl
Normal file
52
charts/open-design/templates/_helpers.tpl
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "open-design.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
*/}}
|
||||
{{- define "open-design.fullname" -}}
|
||||
{{- if .Values.fullnameOverride }}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||
{{- if contains $name .Release.Name }}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create chart name and version as used by the chart label.
|
||||
*/}}
|
||||
{{- define "open-design.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Common labels injected into all resources.
|
||||
*/}}
|
||||
{{- define "open-design.labels" -}}
|
||||
helm.sh/chart: {{ include "open-design.chart" . }}
|
||||
{{ include "open-design.selectorLabels" . }}
|
||||
{{- if .Values.image.tag }}
|
||||
app.kubernetes.io/version: {{ .Values.image.tag | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- with .Values.commonLabels }}
|
||||
{{ toYaml . }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Selector labels used by Services and HPAs.
|
||||
*/}}
|
||||
{{- define "open-design.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "open-design.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
59
charts/open-design/templates/configmap.yaml
Normal file
59
charts/open-design/templates/configmap.yaml
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ include "open-design.fullname" . }}
|
||||
labels:
|
||||
{{- include "open-design.labels" . | nindent 4 }}
|
||||
data:
|
||||
OD_BIND_HOST: {{ .Values.config.bindHost | quote }}
|
||||
OD_PORT: {{ .Values.config.webPort | quote }}
|
||||
OD_WEB_PORT: {{ .Values.config.webPort | quote }}
|
||||
PROXY_PORT: {{ .Values.authProxy.port | quote }}
|
||||
NODE_ENV: {{ .Values.config.nodeEnv | quote }}
|
||||
NODE_OPTIONS: {{ .Values.config.nodeOptions | quote }}
|
||||
{{- $publicBaseUrl := .Values.config.publicBaseUrl }}
|
||||
{{- if and (not $publicBaseUrl) .Values.ingress.enabled .Values.ingress.hosts }}
|
||||
{{- if eq (len .Values.ingress.hosts) 1 }}
|
||||
{{- $singleHost := (index .Values.ingress.hosts 0).host }}
|
||||
{{- $isSecure := false }}
|
||||
{{- range $.Values.ingress.tls }}
|
||||
{{- if has $singleHost .hosts }}
|
||||
{{- $isSecure = true }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- $scheme := ternary "https://" "http://" $isSecure }}
|
||||
{{- $publicBaseUrl = printf "%s%s" $scheme $singleHost }}
|
||||
{{- else if gt (len .Values.ingress.hosts) 1 }}
|
||||
{{- fail "CRITICAL ERROR: Multiple ingress hosts configured. You must explicitly set config.publicBaseUrl to define the canonical origin for OAuth callbacks." }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if $publicBaseUrl }}
|
||||
OD_PUBLIC_BASE_URL: {{ $publicBaseUrl | quote }}
|
||||
{{- end }}
|
||||
|
||||
{{- $origins := .Values.config.allowedOrigins }}
|
||||
{{- if and .Values.ingress.enabled (not $origins) }}
|
||||
{{- $hostList := list }}
|
||||
{{- range .Values.ingress.hosts }}
|
||||
{{- $currentHost := .host }}
|
||||
{{- $isSecure := false }}
|
||||
{{- range $.Values.ingress.tls }}
|
||||
{{- if has $currentHost .hosts }}
|
||||
{{- $isSecure = true }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- $scheme := ternary "https://" "http://" $isSecure }}
|
||||
{{- $hostList = append $hostList (printf "%s%s" $scheme $currentHost) }}
|
||||
{{- end }}
|
||||
{{- $origins = join "," $hostList }}
|
||||
{{- else if not $origins }}
|
||||
{{- if or (eq .Values.service.type "LoadBalancer") (eq .Values.service.type "NodePort") }}
|
||||
{{- fail "CRITICAL ERROR: service.type is set to LoadBalancer or NodePort but config.allowedOrigins is empty. You must explicitly configure config.allowedOrigins when exposing the service directly without an Ingress." }}
|
||||
{{- else }}
|
||||
{{- /* Synthesize default local origins for port-forwarding when Ingress is disabled */ -}}
|
||||
{{- $localProxy := printf "http://localhost:%v" .Values.authProxy.port }}
|
||||
{{- $localSvc := printf "http://localhost:%v" .Values.service.port }}
|
||||
{{- $origins = printf "%s,%s" $localProxy $localSvc }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
OD_ALLOWED_ORIGINS: {{ $origins | quote }}
|
||||
162
charts/open-design/templates/deployment.yaml
Normal file
162
charts/open-design/templates/deployment.yaml
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "open-design.fullname" . }}
|
||||
labels:
|
||||
{{- include "open-design.labels" . | nindent 4 }}
|
||||
|
||||
spec:
|
||||
{{- if not .Values.hpa.enabled }}
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
{{- end }}
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "open-design.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
|
||||
checksum/proxy-config: {{ include (print $.Template.BasePath "/proxy-configmap.yaml") . | sha256sum }}
|
||||
checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
|
||||
labels:
|
||||
{{- include "open-design.selectorLabels" . | nindent 8 }}
|
||||
spec:
|
||||
{{- with .Values.podSecurityContext }}
|
||||
securityContext:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
initContainers:
|
||||
{{- if .Values.initContainers }}
|
||||
{{- toYaml .Values.initContainers | nindent 8 }}
|
||||
{{- end }}
|
||||
- name: auth-proxy-init
|
||||
image: {{ .Values.authProxy.image }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.authProxy.securityContext | nindent 12 }}
|
||||
command: ["/bin/sh", "-c"]
|
||||
args:
|
||||
- envsubst '$PROXY_PORT $OD_BIND_HOST $OD_WEB_PORT $PROXY_API_TOKEN' < /etc/nginx/templates/default.conf.template > /etc/nginx/conf.d/default.conf
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: {{ include "open-design.fullname" . }}
|
||||
env:
|
||||
- name: PROXY_API_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "open-design.fullname" . }}
|
||||
key: OD_API_TOKEN
|
||||
volumeMounts:
|
||||
- name: proxy-template
|
||||
mountPath: /etc/nginx/templates
|
||||
- name: proxy-config
|
||||
mountPath: /etc/nginx/conf.d
|
||||
- name: proxy-tmp
|
||||
mountPath: /tmp
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.containerSecurityContext | nindent 12 }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: {{ .Values.config.webPort }}
|
||||
protocol: TCP
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: {{ include "open-design.fullname" . }}
|
||||
env:
|
||||
- name: OD_API_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "open-design.fullname" . }}
|
||||
key: OD_API_TOKEN
|
||||
{{- if .Values.livenessProbe.enabled }}
|
||||
livenessProbe:
|
||||
exec:
|
||||
command: ["wget", "-qO-", "http://{{ .Values.config.bindHost }}:{{ .Values.config.webPort }}/api/health"]
|
||||
initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }}
|
||||
periodSeconds: {{ .Values.livenessProbe.periodSeconds }}
|
||||
timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }}
|
||||
failureThreshold: {{ .Values.livenessProbe.failureThreshold }}
|
||||
{{- end }}
|
||||
{{- if .Values.readinessProbe.enabled }}
|
||||
readinessProbe:
|
||||
exec:
|
||||
command: ["wget", "-qO-", "http://{{ .Values.config.bindHost }}:{{ .Values.config.webPort }}/api/health"]
|
||||
initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }}
|
||||
periodSeconds: {{ .Values.readinessProbe.periodSeconds }}
|
||||
timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }}
|
||||
failureThreshold: {{ .Values.readinessProbe.failureThreshold }}
|
||||
{{- end }}
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
volumeMounts:
|
||||
- name: storage
|
||||
mountPath: /app/.od
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
- name: auth-proxy
|
||||
image: {{ .Values.authProxy.image }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.authProxy.securityContext | nindent 12 }}
|
||||
ports:
|
||||
- name: http-proxy
|
||||
containerPort: {{ .Values.authProxy.port }}
|
||||
protocol: TCP
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /nginx-health
|
||||
port: http-proxy
|
||||
initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }}
|
||||
periodSeconds: {{ .Values.livenessProbe.periodSeconds }}
|
||||
timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }}
|
||||
failureThreshold: {{ .Values.livenessProbe.failureThreshold }}
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /api/health
|
||||
port: http-proxy
|
||||
initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }}
|
||||
periodSeconds: {{ .Values.readinessProbe.periodSeconds }}
|
||||
timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }}
|
||||
failureThreshold: {{ .Values.readinessProbe.failureThreshold }}
|
||||
volumeMounts:
|
||||
- name: proxy-config
|
||||
mountPath: /etc/nginx/conf.d
|
||||
readOnly: true
|
||||
- name: proxy-tmp
|
||||
mountPath: /tmp
|
||||
{{- if .Values.sidecars }}
|
||||
{{- toYaml .Values.sidecars | nindent 8 }}
|
||||
{{- end }}
|
||||
volumes:
|
||||
- name: storage
|
||||
{{- if .Values.persistence.enabled }}
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ include "open-design.fullname" . }}
|
||||
{{- else }}
|
||||
emptyDir: {}
|
||||
{{- end }}
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
- name: proxy-template
|
||||
configMap:
|
||||
name: {{ include "open-design.fullname" . }}-proxy
|
||||
- name: proxy-config
|
||||
emptyDir: {}
|
||||
- name: proxy-tmp
|
||||
emptyDir: {}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
30
charts/open-design/templates/hpa.yaml
Normal file
30
charts/open-design/templates/hpa.yaml
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# WARNING: SQLite does not support concurrent writes from multiple replicas.
|
||||
# Only enable HPA if open-design has been configured with an external database.
|
||||
{{- if .Values.hpa.enabled }}
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: {{ include "open-design.fullname" . }}
|
||||
labels:
|
||||
{{- include "open-design.labels" . | nindent 4 }}
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: {{ include "open-design.fullname" . }}
|
||||
minReplicas: {{ .Values.hpa.minReplicas }}
|
||||
maxReplicas: {{ .Values.hpa.maxReplicas }}
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: {{ .Values.hpa.targetCPUUtilizationPercentage }}
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: {{ .Values.hpa.targetMemoryUtilizationPercentage }}
|
||||
{{- end }}
|
||||
48
charts/open-design/templates/ingress.yaml
Normal file
48
charts/open-design/templates/ingress.yaml
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
{{- if .Values.ingress.enabled -}}
|
||||
{{- range .Values.ingress.hosts }}
|
||||
{{- range .paths }}
|
||||
{{- if ne .path "/" }}
|
||||
{{- fail "CRITICAL ERROR: Non-root ingress path prefixes (sub-paths) are not supported by the proxy routing stack. Ingress paths must be fixed to '/'." }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ include "open-design.fullname" . }}
|
||||
labels:
|
||||
{{- include "open-design.labels" . | nindent 4 }}
|
||||
{{- with .Values.ingress.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if .Values.ingress.className }}
|
||||
ingressClassName: {{ .Values.ingress.className }}
|
||||
{{- end }}
|
||||
{{- if .Values.ingress.tls }}
|
||||
tls:
|
||||
{{- range .Values.ingress.tls }}
|
||||
- hosts:
|
||||
{{- range .hosts }}
|
||||
- {{ . | quote }}
|
||||
{{- end }}
|
||||
secretName: {{ .secretName }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- range .Values.ingress.hosts }}
|
||||
- host: {{ .host | quote }}
|
||||
http:
|
||||
paths:
|
||||
{{- range .paths }}
|
||||
- path: {{ .path }}
|
||||
pathType: {{ .pathType }}
|
||||
backend:
|
||||
service:
|
||||
name: {{ include "open-design.fullname" $ }}
|
||||
port:
|
||||
name: http
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
43
charts/open-design/templates/proxy-configmap.yaml
Normal file
43
charts/open-design/templates/proxy-configmap.yaml
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ include "open-design.fullname" . }}-proxy
|
||||
labels:
|
||||
{{- include "open-design.labels" . | nindent 4 }}
|
||||
data:
|
||||
default.conf.template: |
|
||||
server {
|
||||
listen ${PROXY_PORT};
|
||||
|
||||
location /nginx-health {
|
||||
return 200 "healthy\n";
|
||||
add_header Content-Type text/plain;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://${OD_BIND_HOST}:${OD_WEB_PORT};
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
|
||||
proxy_set_header X-Forwarded-Host $http_x_forwarded_host;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://${OD_BIND_HOST}:${OD_WEB_PORT};
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
|
||||
proxy_set_header X-Forwarded-Host $http_x_forwarded_host;
|
||||
|
||||
proxy_set_header Authorization "Bearer ${PROXY_API_TOKEN}";
|
||||
|
||||
# Critical for Server-Sent Events (SSE)
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 600s;
|
||||
proxy_send_timeout 600s;
|
||||
proxy_set_header Connection '';
|
||||
http2_push_preload on;
|
||||
}
|
||||
}
|
||||
17
charts/open-design/templates/pvc.yaml
Normal file
17
charts/open-design/templates/pvc.yaml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{{- if .Values.persistence.enabled -}}
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: {{ include "open-design.fullname" . }}
|
||||
labels:
|
||||
{{- include "open-design.labels" . | nindent 4 }}
|
||||
spec:
|
||||
accessModes:
|
||||
- {{ .Values.persistence.accessMode }}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.persistence.size | quote }}
|
||||
{{- if .Values.persistence.storageClass }}
|
||||
storageClassName: {{ .Values.persistence.storageClass | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
11
charts/open-design/templates/secret.yaml
Normal file
11
charts/open-design/templates/secret.yaml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ include "open-design.fullname" . }}
|
||||
type: Opaque
|
||||
data:
|
||||
{{- if .Values.config.apiToken }}
|
||||
OD_API_TOKEN: {{ .Values.config.apiToken | b64enc | quote }}
|
||||
{{- else }}
|
||||
{{- fail "CRITICAL ERROR: config.apiToken cannot be empty." }}
|
||||
{{- end }}
|
||||
15
charts/open-design/templates/service.yaml
Normal file
15
charts/open-design/templates/service.yaml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "open-design.fullname" . }}
|
||||
labels:
|
||||
{{- include "open-design.labels" . | nindent 4 }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
ports:
|
||||
- port: {{ .Values.service.port }}
|
||||
targetPort: http-proxy
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
{{- include "open-design.selectorLabels" . | nindent 4 }}
|
||||
120
charts/open-design/values.yaml
Normal file
120
charts/open-design/values.yaml
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
# Safety constraint for SQLite data consistency
|
||||
replicaCount: 1
|
||||
|
||||
commonLabels:
|
||||
app.kubernetes.io/environment: "production"
|
||||
|
||||
image:
|
||||
repository: vanjayak/open-design
|
||||
pullPolicy: IfNotPresent
|
||||
tag: "latest"
|
||||
|
||||
config:
|
||||
# The runtime environment for the Node.js process.
|
||||
# Must be either "production" or "development".
|
||||
# For Staging clusters, leave this as "production".
|
||||
nodeEnv: "production"
|
||||
allowedOrigins: ""
|
||||
publicBaseUrl: ""
|
||||
nodeOptions: "--max-old-space-size=192"
|
||||
webPort: 7456
|
||||
bindHost: "127.0.0.1"
|
||||
apiToken: "secure-default-token-change-me"
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 80
|
||||
|
||||
persistence:
|
||||
enabled: true
|
||||
storageClass: ""
|
||||
accessMode: ReadWriteOnce
|
||||
size: 10Gi
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
className: "nginx"
|
||||
annotations:
|
||||
# Critical: Disables proxy buffering so Server-Sent Events (SSE) stream in real-time
|
||||
nginx.ingress.kubernetes.io/proxy-buffering: "off"
|
||||
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
|
||||
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
|
||||
hosts:
|
||||
- host: open-design.local
|
||||
paths:
|
||||
- path: /
|
||||
pathType: ImplementationSpecific
|
||||
tls: []
|
||||
## example for tls configuration:
|
||||
# - secretName: open-design-tls
|
||||
# hosts:
|
||||
# - open-design.local
|
||||
|
||||
resources:
|
||||
limits:
|
||||
cpu: 1000m
|
||||
memory: 1024Mi
|
||||
requests:
|
||||
cpu: 200m
|
||||
memory: 256Mi
|
||||
|
||||
containerSecurityContext:
|
||||
seLinuxOptions: {}
|
||||
runAsUser: 1001
|
||||
runAsGroup: 1001
|
||||
runAsNonRoot: true
|
||||
privileged: false
|
||||
readOnlyRootFilesystem: true
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
seccompProfile:
|
||||
type: "RuntimeDefault"
|
||||
|
||||
podSecurityContext:
|
||||
fsGroupChangePolicy: Always
|
||||
sysctls: []
|
||||
supplementalGroups: []
|
||||
fsGroup: 1001
|
||||
|
||||
authProxy:
|
||||
image: "nginxinc/nginx-unprivileged:1.25-alpine-slim"
|
||||
port: 8080
|
||||
securityContext:
|
||||
runAsUser: 101
|
||||
runAsGroup: 101
|
||||
runAsNonRoot: true
|
||||
readOnlyRootFilesystem: true
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
|
||||
livenessProbe:
|
||||
enabled: true
|
||||
initialDelaySeconds: 20
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
|
||||
readinessProbe:
|
||||
enabled: true
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
|
||||
# WARNING: Do not enable HPA if using the default SQLite storage.
|
||||
# Concurrent writes from multiple pods will corrupt the database file.
|
||||
hpa:
|
||||
enabled: false
|
||||
minReplicas: 1
|
||||
maxReplicas: 5
|
||||
targetCPUUtilizationPercentage: 80
|
||||
targetMemoryUtilizationPercentage: 80
|
||||
|
||||
nodeSelector: {}
|
||||
affinity: {}
|
||||
tolerations: []
|
||||
|
||||
initContainers: []
|
||||
sidecars: []
|
||||
Loading…
Reference in a new issue