Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions helm/agentflow/templates/_env.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
{{/*
Common container env for API and worker Deployments.
Caller: include "agentflow.containerEnv" (dict "root" . "secretName" $secretName "processRole" "api")
processRole may be "" (omit AGENTFLOW_PROCESS_ROLE — app default 'all').
*/}}
{{- define "agentflow.containerEnv" -}}
{{- $root := .root -}}
{{- $secretName := .secretName -}}
{{- $processRole := .processRole | default "" -}}
- name: SERVING_BACKEND
value: {{ $root.Values.serving.backend | quote }}
{{- if eq $root.Values.serving.backend "clickhouse" }}
- name: CLICKHOUSE_HOST
value: {{ required "serving.clickhouse.host is required when serving.backend=clickhouse" $root.Values.serving.clickhouse.host | quote }}
- name: CLICKHOUSE_PORT
value: {{ $root.Values.serving.clickhouse.port | quote }}
- name: CLICKHOUSE_DATABASE
value: {{ $root.Values.serving.clickhouse.database | quote }}
- name: CLICKHOUSE_USER
value: {{ $root.Values.serving.clickhouse.user | quote }}
{{- if $root.Values.serving.clickhouse.existingSecret }}
- name: CLICKHOUSE_PASSWORD
valueFrom:
secretKeyRef:
name: {{ $root.Values.serving.clickhouse.existingSecret | quote }}
key: {{ $root.Values.serving.clickhouse.passwordKey | quote }}
{{- end }}
- name: CLICKHOUSE_SECURE
value: {{ $root.Values.serving.clickhouse.secure | quote }}
{{- if $root.Values.serving.clickhouse.tls.caSecret }}
- name: CLICKHOUSE_CA_CERT
value: /etc/agentflow/tls/clickhouse/{{ $root.Values.serving.clickhouse.tls.caKey }}
{{- end }}
{{- end }}
- name: AGENTFLOW_CONTROLPLANE_STORE
value: {{ $root.Values.controlPlane.store | quote }}
{{- if eq $root.Values.controlPlane.store "postgres" }}
- name: AGENTFLOW_CONTROLPLANE_PG_DSN
valueFrom:
secretKeyRef:
name: {{ required "controlPlane.postgres.existingSecret is required when controlPlane.store=postgres (it must hold the PostgreSQL DSN; the chart ships no PG service)" $root.Values.controlPlane.postgres.existingSecret | quote }}
key: {{ $root.Values.controlPlane.postgres.dsnKey | quote }}
{{- end }}
{{- if $processRole }}
- name: AGENTFLOW_PROCESS_ROLE
value: {{ $processRole | quote }}
{{- end }}
- name: DUCKDB_PATH
value: {{ $root.Values.config.duckdbPath | quote }}
- name: AGENTFLOW_USAGE_DB_PATH
value: {{ $root.Values.config.usageDbPath | quote }}
- name: AGENTFLOW_API_KEYS_FILE
value: {{ $root.Values.config.apiKeysPath | quote }}
- name: AGENTFLOW_TENANTS_FILE
value: {{ $root.Values.config.tenantsPath | quote }}
- name: AGENTFLOW_SLO_FILE
value: {{ $root.Values.config.sloPath | quote }}
- name: AGENTFLOW_SECURITY_CONFIG_FILE
value: {{ $root.Values.config.securityPath | quote }}
- name: AGENTFLOW_API_VERSIONS_FILE
value: {{ $root.Values.config.apiVersionsPath | quote }}
- name: AGENTFLOW_CONTRACTS_DIR
value: {{ $root.Values.config.contractsDir | quote }}
- name: AGENTFLOW_RATE_LIMIT_RPM
value: {{ $root.Values.config.rateLimitRpm | quote }}
- name: CACHE_TTL_SECONDS
value: {{ $root.Values.config.cacheTtlSeconds | quote }}
{{- if $root.Values.config.profile }}
- name: AGENTFLOW_PROFILE
value: {{ $root.Values.config.profile | quote }}
{{- end }}
- name: AGENTFLOW_CORS_ORIGINS
value: {{ $root.Values.config.corsOrigins | quote }}
- name: AGENTFLOW_ADMIN_KEY
valueFrom:
secretKeyRef:
name: {{ $secretName }}
key: admin-key
{{- if $root.Values.config.redisUrl }}
- name: REDIS_URL
value: {{ $root.Values.config.redisUrl | quote }}
{{- end }}
{{- if $root.Values.config.otlpEndpoint }}
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: {{ $root.Values.config.otlpEndpoint | quote }}
- name: OTEL_SERVICE_NAME
value: {{ $root.Values.config.otlpServiceName | quote }}
{{- end }}
{{- with $root.Values.extraEnv }}
{{/* Leading newline required: {{- toYaml }} would glue onto the previous value. */}}
{{ toYaml . }}
{{- end }}
{{- end -}}
145 changes: 145 additions & 0 deletions helm/agentflow/templates/deployment-worker.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
{{- if .Values.worker.enabled }}
{{- if ne .Values.controlPlane.store "postgres" }}
{{- fail "worker.enabled requires controlPlane.store=postgres (AGENTFLOW_PROCESS_ROLE=api/worker needs a shared control plane; the embedded profile is single-process)." }}
{{- end }}
{{- $secretName := .Values.secrets.existingSecret | default (include "agentflow.fullname" .) }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "agentflow.fullname" . }}-worker
labels:
{{- include "agentflow.labels" . | nindent 4 }}
app.kubernetes.io/component: worker
spec:
# Delivery loops claim rows in PostgreSQL; keep replicaCount small (default 1).
# Scaling workers multiplies claim concurrency, not request capacity.
replicas: {{ .Values.worker.replicaCount }}
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
selector:
matchLabels:
{{- include "agentflow.selectorLabels" . | nindent 6 }}
app.kubernetes.io/component: worker
template:
metadata:
labels:
{{- include "agentflow.selectorLabels" . | nindent 8 }}
app.kubernetes.io/component: worker
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
{{- if .Values.secrets.create }}
checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
{{- end }}
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
serviceAccountName: {{ include "agentflow.serviceAccountName" . }}
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.podSecurityContext }}
securityContext:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: agentflow-worker
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
{{- with .Values.containerSecurityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
command:
- uvicorn
- src.serving.api.main:app
- --host
- 0.0.0.0
- --port
- "8000"
ports:
- name: http
containerPort: 8000
protocol: TCP
env:
{{- include "agentflow.containerEnv" (dict "root" . "secretName" $secretName "processRole" "worker") | nindent 12 }}
# Worker does not take Service traffic; probes stay dependency-light
# so a serving-store blip does not thrash delivery.
readinessProbe:
httpGet:
path: /health/live
port: http
initialDelaySeconds: {{ .Values.probes.readiness.initialDelaySeconds }}
periodSeconds: {{ .Values.probes.readiness.periodSeconds }}
timeoutSeconds: {{ .Values.probes.readiness.timeoutSeconds }}
failureThreshold: {{ .Values.probes.readiness.failureThreshold }}
livenessProbe:
httpGet:
path: /health/live
port: http
initialDelaySeconds: {{ .Values.probes.liveness.initialDelaySeconds }}
periodSeconds: {{ .Values.probes.liveness.periodSeconds }}
timeoutSeconds: {{ .Values.probes.liveness.timeoutSeconds }}
failureThreshold: {{ .Values.probes.liveness.failureThreshold }}
resources:
{{- toYaml (.Values.worker.resources | default .Values.resources) | nindent 12 }}
volumeMounts:
- name: data
mountPath: {{ .Values.persistence.mountPath | quote }}
- name: config
mountPath: /etc/agentflow/config
readOnly: true
- name: secrets
mountPath: /etc/agentflow/secret
readOnly: true
{{- if and (eq .Values.serving.backend "clickhouse") .Values.serving.clickhouse.tls.caSecret }}
- name: clickhouse-ca
mountPath: /etc/agentflow/tls/clickhouse
readOnly: true
{{- end }}
{{- if .Values.tmpVolume.enabled }}
- name: tmp
mountPath: /tmp
{{- end }}
volumes:
- name: config
configMap:
name: {{ include "agentflow.fullname" . }}
- name: secrets
secret:
secretName: {{ $secretName }}
{{- if and (eq .Values.serving.backend "clickhouse") .Values.serving.clickhouse.tls.caSecret }}
- name: clickhouse-ca
secret:
secretName: {{ .Values.serving.clickhouse.tls.caSecret | quote }}
{{- end }}
# Never share the API PVC: RWO multi-attach fails, and the worker
# does not own serving state on the scale (postgres) profile.
- name: data
emptyDir: {}
{{- if .Values.tmpVolume.enabled }}
- name: tmp
emptyDir:
medium: Memory
sizeLimit: {{ .Values.tmpVolume.sizeLimit | quote }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}
97 changes: 14 additions & 83 deletions helm/agentflow/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,20 @@
{{- if and (or (gt (int .Values.replicaCount) 1) (and .Values.autoscaling.enabled (gt (int .Values.autoscaling.maxReplicas) 1))) (or (ne .Values.controlPlane.store "postgres") (ne .Values.serving.backend "clickhouse")) }}
{{- fail "Multi-replica requires BOTH an external serving engine (serving.backend=clickhouse, ADR 0006/0007) AND an external control-plane store (controlPlane.store=postgres, ADR 0009/0010): the embedded per-pod store forks webhook/alert/outbox/usage state across replicas (duplicate deliveries, split alert history). The app-side postgres adapter shipped with ADR 0010 slice 5; until the chart profile for it lands (rollout slice 6), keep replicaCount=1 and autoscaling.maxReplicas=1." }}
{{- end }}
{{- if and .Values.worker.enabled (ne .Values.controlPlane.store "postgres") }}
{{- fail "worker.enabled requires controlPlane.store=postgres (AGENTFLOW_PROCESS_ROLE=api/worker needs a shared control plane; the embedded profile is single-process)." }}
{{- end }}
{{- $secretName := .Values.secrets.existingSecret | default (include "agentflow.fullname" .) }}
{{- $apiProcessRole := ternary "api" "" .Values.worker.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "agentflow.fullname" . }}
labels:
{{- include "agentflow.labels" . | nindent 4 }}
{{- if .Values.worker.enabled }}
app.kubernetes.io/component: api
{{- end }}
spec:
replicas: {{ .Values.replicaCount }}
strategy:
Expand All @@ -21,10 +28,16 @@ spec:
selector:
matchLabels:
{{- include "agentflow.selectorLabels" . | nindent 6 }}
{{- if .Values.worker.enabled }}
app.kubernetes.io/component: api
{{- end }}
template:
metadata:
labels:
{{- include "agentflow.selectorLabels" . | nindent 8 }}
{{- if .Values.worker.enabled }}
app.kubernetes.io/component: api
{{- end }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
Expand Down Expand Up @@ -66,89 +79,7 @@ spec:
containerPort: 8000
protocol: TCP
env:
- name: SERVING_BACKEND
value: {{ .Values.serving.backend | quote }}
{{- if eq .Values.serving.backend "clickhouse" }}
- name: CLICKHOUSE_HOST
value: {{ required "serving.clickhouse.host is required when serving.backend=clickhouse" .Values.serving.clickhouse.host | quote }}
- name: CLICKHOUSE_PORT
value: {{ .Values.serving.clickhouse.port | quote }}
- name: CLICKHOUSE_DATABASE
value: {{ .Values.serving.clickhouse.database | quote }}
- name: CLICKHOUSE_USER
value: {{ .Values.serving.clickhouse.user | quote }}
{{- if .Values.serving.clickhouse.existingSecret }}
- name: CLICKHOUSE_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Values.serving.clickhouse.existingSecret | quote }}
key: {{ .Values.serving.clickhouse.passwordKey | quote }}
{{- end }}
- name: CLICKHOUSE_SECURE
value: {{ .Values.serving.clickhouse.secure | quote }}
{{- if .Values.serving.clickhouse.tls.caSecret }}
- name: CLICKHOUSE_CA_CERT
value: /etc/agentflow/tls/clickhouse/{{ .Values.serving.clickhouse.tls.caKey }}
{{- end }}
{{- end }}
# Control-plane store (ADR 0010). Set explicitly (mirrors
# SERVING_BACKEND) so the rendered manifest is self-documenting; the
# app treats an unset value as 'embedded'. The postgres profile also
# sources its DSN from an operator-provided secret — the chart ships
# no PostgreSQL service, exactly as it ships no ClickHouse.
- name: AGENTFLOW_CONTROLPLANE_STORE
value: {{ .Values.controlPlane.store | quote }}
{{- if eq .Values.controlPlane.store "postgres" }}
- name: AGENTFLOW_CONTROLPLANE_PG_DSN
valueFrom:
secretKeyRef:
name: {{ required "controlPlane.postgres.existingSecret is required when controlPlane.store=postgres (it must hold the PostgreSQL DSN; the chart ships no PG service)" .Values.controlPlane.postgres.existingSecret | quote }}
key: {{ .Values.controlPlane.postgres.dsnKey | quote }}
{{- end }}
- name: DUCKDB_PATH
value: {{ .Values.config.duckdbPath | quote }}
- name: AGENTFLOW_USAGE_DB_PATH
value: {{ .Values.config.usageDbPath | quote }}
- name: AGENTFLOW_API_KEYS_FILE
value: {{ .Values.config.apiKeysPath | quote }}
- name: AGENTFLOW_TENANTS_FILE
value: {{ .Values.config.tenantsPath | quote }}
- name: AGENTFLOW_SLO_FILE
value: {{ .Values.config.sloPath | quote }}
- name: AGENTFLOW_SECURITY_CONFIG_FILE
value: {{ .Values.config.securityPath | quote }}
- name: AGENTFLOW_API_VERSIONS_FILE
value: {{ .Values.config.apiVersionsPath | quote }}
- name: AGENTFLOW_CONTRACTS_DIR
value: {{ .Values.config.contractsDir | quote }}
- name: AGENTFLOW_RATE_LIMIT_RPM
value: {{ .Values.config.rateLimitRpm | quote }}
- name: CACHE_TTL_SECONDS
value: {{ .Values.config.cacheTtlSeconds | quote }}
{{- if .Values.config.profile }}
- name: AGENTFLOW_PROFILE
value: {{ .Values.config.profile | quote }}
{{- end }}
- name: AGENTFLOW_CORS_ORIGINS
value: {{ .Values.config.corsOrigins | quote }}
- name: AGENTFLOW_ADMIN_KEY
valueFrom:
secretKeyRef:
name: {{ $secretName }}
key: admin-key
{{- if .Values.config.redisUrl }}
- name: REDIS_URL
value: {{ .Values.config.redisUrl | quote }}
{{- end }}
{{- if .Values.config.otlpEndpoint }}
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: {{ .Values.config.otlpEndpoint | quote }}
- name: OTEL_SERVICE_NAME
value: {{ .Values.config.otlpServiceName | quote }}
{{- end }}
{{- with .Values.extraEnv }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- include "agentflow.containerEnv" (dict "root" . "secretName" $secretName "processRole" $apiProcessRole) | nindent 12 }}
# Both probes pointed at /v1/health, which always answers 200 — its
# status lives in the payload. So a pod with an unreachable serving
# store stayed Ready and kept taking traffic, and liveness could never
Expand Down
4 changes: 4 additions & 0 deletions helm/agentflow/templates/service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ spec:
name: http
selector:
{{- include "agentflow.selectorLabels" . | nindent 4 }}
{{- if .Values.worker.enabled }}
# Exclude worker pods (delivery loops only; same selectorLabels family).
app.kubernetes.io/component: api
{{- end }}
Loading