feat(advisory-ai): Add deployment guide, Dockerfile, and Helm chart for on-prem packaging
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled

- Introduced a comprehensive deployment guide for AdvisoryAI, detailing local builds, remote inference toggles, and scaling guidance.
- Created a multi-role Dockerfile for building WebService and Worker images.
- Added a docker-compose file for local and offline deployment.
- Implemented a Helm chart for Kubernetes deployment with persistence and remote inference options.
- Established a new API endpoint `/advisories/summary` for deterministic summaries of observations and linksets.
- Introduced a JSON schema for risk profiles and a validator to ensure compliance with the schema.
- Added unit tests for the risk profile validator to ensure functionality and error handling.
This commit is contained in:
StellaOps Bot
2025-11-23 00:35:33 +02:00
parent 2e89a92d92
commit 8d78dd219b
33 changed files with 1254 additions and 259 deletions

View File

@@ -0,0 +1,6 @@
apiVersion: v2
name: stellaops-advisoryai
version: 0.1.0
appVersion: "0.1.0"
description: AdvisoryAI WebService + Worker packaging for on-prem/air-gapped installs.
type: application

View File

@@ -0,0 +1,12 @@
{{- define "stellaops-advisoryai.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- define "stellaops-advisoryai.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}

View File

@@ -0,0 +1,71 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "stellaops-advisoryai.fullname" . }}
labels:
app.kubernetes.io/name: {{ include "stellaops-advisoryai.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion }}
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: {{ include "stellaops-advisoryai.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "stellaops-advisoryai.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
containers:
- name: web
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
env:
- name: ASPNETCORE_URLS
value: "http://0.0.0.0:{{ .Values.service.port }}"
- name: ADVISORYAI__INFERENCE__MODE
value: "{{ .Values.inference.mode }}"
- name: ADVISORYAI__INFERENCE__REMOTE__BASEADDRESS
value: "{{ .Values.inference.remote.baseAddress }}"
- name: ADVISORYAI__INFERENCE__REMOTE__ENDPOINT
value: "{{ .Values.inference.remote.endpoint }}"
- name: ADVISORYAI__INFERENCE__REMOTE__APIKEY
value: "{{ .Values.inference.remote.apiKey }}"
- name: ADVISORYAI__INFERENCE__REMOTE__TIMEOUT
value: "{{ printf "00:00:%d" .Values.inference.remote.timeoutSeconds }}"
- name: ADVISORYAI__STORAGE__PLANCACHEDIRECTORY
value: {{ .Values.storage.planCachePath | quote }}
- name: ADVISORYAI__STORAGE__OUTPUTDIRECTORY
value: {{ .Values.storage.outputPath | quote }}
- name: ADVISORYAI__QUEUE__DIRECTORYPATH
value: {{ .Values.storage.queuePath | quote }}
envFrom:
{{- if .Values.extraEnvFrom }}
- secretRef:
name: {{ .Values.extraEnvFrom | first }}
{{- end }}
{{- if .Values.extraEnv }}
{{- range .Values.extraEnv }}
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}
{{- end }}
ports:
- containerPort: {{ .Values.service.port }}
volumeMounts:
- name: advisoryai-data
mountPath: /app/data
resources: {{- toYaml .Values.resources | nindent 12 }}
volumes:
- name: advisoryai-data
{{- if .Values.storage.persistence.enabled }}
persistentVolumeClaim:
claimName: {{ .Values.storage.persistence.existingClaim | default (printf "%s-data" (include "stellaops-advisoryai.fullname" .)) }}
{{- else }}
emptyDir: {}
{{- end }}
nodeSelector: {{- toYaml .Values.nodeSelector | nindent 8 }}
tolerations: {{- toYaml .Values.tolerations | nindent 8 }}
affinity: {{- toYaml .Values.affinity | nindent 8 }}

View File

@@ -0,0 +1,15 @@
{{- if and .Values.storage.persistence.enabled (not .Values.storage.persistence.existingClaim) }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ printf "%s-data" (include "stellaops-advisoryai.fullname" .) }}
labels:
app.kubernetes.io/name: {{ include "stellaops-advisoryai.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: {{ .Values.storage.persistence.size }}
{{- end }}

View File

@@ -0,0 +1,17 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "stellaops-advisoryai.fullname" . }}
labels:
app.kubernetes.io/name: {{ include "stellaops-advisoryai.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
type: {{ .Values.service.type }}
selector:
app.kubernetes.io/name: {{ include "stellaops-advisoryai.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
ports:
- name: http
port: {{ .Values.service.port }}
targetPort: {{ .Values.service.port }}
protocol: TCP

View File

@@ -0,0 +1,66 @@
{{- if .Values.worker.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "stellaops-advisoryai.fullname" . }}-worker
labels:
app.kubernetes.io/name: {{ include "stellaops-advisoryai.name" . }}-worker
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
replicas: {{ .Values.worker.replicas }}
selector:
matchLabels:
app.kubernetes.io/name: {{ include "stellaops-advisoryai.name" . }}-worker
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "stellaops-advisoryai.name" . }}-worker
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
containers:
- name: worker
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
command: ["dotnet", "StellaOps.AdvisoryAI.Worker.dll"]
env:
- name: ADVISORYAI__INFERENCE__MODE
value: "{{ .Values.inference.mode }}"
- name: ADVISORYAI__INFERENCE__REMOTE__BASEADDRESS
value: "{{ .Values.inference.remote.baseAddress }}"
- name: ADVISORYAI__INFERENCE__REMOTE__ENDPOINT
value: "{{ .Values.inference.remote.endpoint }}"
- name: ADVISORYAI__INFERENCE__REMOTE__APIKEY
value: "{{ .Values.inference.remote.apiKey }}"
- name: ADVISORYAI__INFERENCE__REMOTE__TIMEOUT
value: "{{ printf "00:00:%d" .Values.inference.remote.timeoutSeconds }}"
- name: ADVISORYAI__STORAGE__PLANCACHEDIRECTORY
value: {{ .Values.storage.planCachePath | quote }}
- name: ADVISORYAI__STORAGE__OUTPUTDIRECTORY
value: {{ .Values.storage.outputPath | quote }}
- name: ADVISORYAI__QUEUE__DIRECTORYPATH
value: {{ .Values.storage.queuePath | quote }}
envFrom:
{{- if .Values.extraEnvFrom }}
- secretRef:
name: {{ .Values.extraEnvFrom | first }}
{{- end }}
{{- if .Values.extraEnv }}
{{- range .Values.extraEnv }}
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}
{{- end }}
volumeMounts:
- name: advisoryai-data
mountPath: /app/data
resources: {{- toYaml .Values.worker.resources | nindent 12 }}
volumes:
- name: advisoryai-data
{{- if .Values.storage.persistence.enabled }}
persistentVolumeClaim:
claimName: {{ .Values.storage.persistence.existingClaim | default (printf "%s-data" (include "stellaops-advisoryai.fullname" .)) }}
{{- else }}
emptyDir: {}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,38 @@
image:
repository: stellaops/advisoryai
tag: dev
pullPolicy: IfNotPresent
service:
port: 8080
type: ClusterIP
inference:
mode: Local # or Remote
remote:
baseAddress: ""
endpoint: "/v1/inference"
apiKey: ""
timeoutSeconds: 30
storage:
planCachePath: /app/data/plans
outputPath: /app/data/outputs
queuePath: /app/data/queue
persistence:
enabled: false
existingClaim: ""
size: 5Gi
resources: {}
nodeSelector: {}
tolerations: []
affinity: {}
worker:
enabled: true
replicas: 1
resources: {}
extraEnv: [] # list of { name: ..., value: ... }
extraEnvFrom: []