fix: Web Widget SDK + Auto-Reply AgentBot sender + LLM 真实模型对接

## 核心修复

### 1. Auto-Reply Sender 修复(所有渠道)
- AutoReplyListener.sendAutoReply() 通过 botInboxRepo 查询 inbox 关联的 AgentBot
- 使用正确的 SenderType="AgentBot"(非小写 agent_bot)传递真实 AgentBot ID
- bootstrap 注入 agentBotInboxRepo/agentBotRepo 依赖

### 2. 事件数据 BUG 修复(影响所有 Webhook 渠道)
- incoming_persister.dispatch(): 补全 sender_type/content 到 event.Data
- channel/webhook.go: HandleWebhook 同步分发也补全 sender_type/content
- 未补全前 AutoReplyListener 找不到字段直接跳过

### 3. Web Widget SDK 生产验证修复
- cookie → localStorage token 同步(frontend/index.html)
- 路由双注册修复(router.go)
- Vite SPA 模式 + /widget 重写(vite.config.ts)
- WidgetService 注入 Dispatcher 触发事件分发

### 4. LLM 真实模型对接
- 配置 deepseek-v4-flash @ http://10.58.144.6:2014/v1
- LLM-mode auto-reply 规则创建并验证通过
- Prompt 文档落地: docs/captain-ai-auto-replay-prompt.md

### 5. 新增基础设施
- Helm chart (deploy/helm/)
- Widget SDK 生产测试页面
- QA 报告

Closes: BUG-W2 (auth sync), BUG-W3 (route double-reg),
       BUG-WEBHOOK-EVENT (missing event data fields)
This commit is contained in:
Rogee
2026-07-28 14:03:19 +08:00
parent 05c5752a2b
commit 9816848ca2
32 changed files with 1688 additions and 15 deletions
+19
View File
@@ -0,0 +1,19 @@
apiVersion: v2
name: gochat
description: GoChat — Open-source customer engagement platform (Go port of Chatwoot)
type: application
version: 0.1.0
appVersion: "1.0.0"
home: https://github.com/gochat/gochat
icon: https://gochat.io/logo.png
maintainers:
- name: gochat-team
email: team@gochat.io
sources:
- https://github.com/gochat/gochat
keywords:
- chat
- customer-engagement
- live-chat
- omnichannel
- chatwoot
+49
View File
@@ -0,0 +1,49 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "gochat.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
*/}}
{{- define "gochat.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 "gochat.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "gochat.labels" -}}
helm.sh/chart: {{ include "gochat.chart" . }}
{{ include "gochat.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "gochat.selectorLabels" -}}
app.kubernetes.io/name: {{ include "gochat.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
@@ -0,0 +1,15 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "gochat.fullname" . }}-config
labels:
{{- include "gochat.labels" . | nindent 4 }}
data:
{{- range $key, $value := .Values.configMap.data }}
{{ $key }}: {{ $value | quote }}
{{- end }}
POSTGRES_HOST: {{ if .Values.postgresql.enabled }}{{ include "gochat.fullname" . }}-postgresql{{ else }}{{ .Values.configMap.data.POSTGRES_HOST | default "localhost" }}{{ end }}
POSTGRES_PORT: "5432"
POSTGRES_DATABASE: {{ .Values.postgresql.auth.database | quote }}
REDIS_HOST: {{ if .Values.redis.enabled }}{{ include "gochat.fullname" . }}-redis-master{{ else }}{{ .Values.configMap.data.REDIS_HOST | default "localhost" }}{{ end }}
REDIS_PORT: "6379"
@@ -0,0 +1,82 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "gochat.fullname" . }}
labels:
{{- include "gochat.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.app.replicaCount }}
selector:
matchLabels:
{{- include "gochat.selectorLabels" . | nindent 6 }}
strategy:
{{- toYaml .Values.app.strategy | nindent 4 }}
template:
metadata:
annotations:
{{- toYaml .Values.app.podAnnotations | nindent 8 }}
labels:
{{- include "gochat.selectorLabels" . | nindent 8 }}
spec:
terminationGracePeriodSeconds: {{ .Values.app.terminationGracePeriodSeconds }}
containers:
- name: gochat
image: "{{ .Values.app.image.repository }}:{{ .Values.app.image.tag }}"
imagePullPolicy: {{ .Values.app.image.pullPolicy }}
ports:
- name: http
containerPort: 3000
protocol: TCP
{{- if .Values.metrics.enabled }}
- name: metrics
containerPort: {{ .Values.metrics.service.port }}
protocol: TCP
{{- end }}
envFrom:
- configMapRef:
name: {{ include "gochat.fullname" . }}-config
- secretRef:
name: {{ include "gochat.fullname" . }}-secret
{{- if .Values.tracing.enabled }}
- configMapRef:
name: {{ include "gochat.fullname" . }}-otel-config
{{- end }}
{{- range $key, $value := .Values.app.extraEnv }}
env:
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep {{ .Values.app.preStopDelaySeconds }}"]
livenessProbe:
{{- toYaml .Values.app.livenessProbe | nindent 12 }}
readinessProbe:
{{- toYaml .Values.app.readinessProbe | nindent 12 }}
resources:
{{- toYaml .Values.app.resources | nindent 12 }}
volumeMounts:
- name: configs
mountPath: /app/configs
- name: migrations
mountPath: /app/migrations
volumes:
- name: configs
configMap:
name: {{ include "gochat.fullname" . }}-configs
- name: migrations
configMap:
name: {{ include "gochat.fullname" . }}-migrations
{{- with .Values.app.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.app.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.app.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
@@ -0,0 +1,27 @@
{{- if and .Values.sealedSecrets.enabled .Values.sealedSecrets.externalSecret.enabled }}
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: {{ include "gochat.fullname" . }}-external-secret
labels:
{{- include "gochat.labels" . | nindent 4 }}
spec:
refreshInterval: {{ .Values.sealedSecrets.externalSecret.refreshInterval }}
secretStoreRef:
name: {{ .Values.sealedSecrets.externalSecret.secretStoreRef.name }}
kind: {{ .Values.sealedSecrets.externalSecret.secretStoreRef.kind }}
target:
name: {{ include "gochat.fullname" . }}-secret
template:
type: Opaque
data:
{{- range $key, $remoteKey := .Values.sealedSecrets.externalSecret.mapping }}
{{ $key }}: "{{ `{{ .` }}{{ $remoteKey }}{{ ` }}` }}"
{{- end }}
data:
{{- range $key, $remoteKey := .Values.sealedSecrets.externalSecret.mapping }}
- secretKey: {{ $key }}
remoteRef:
key: {{ $remoteKey }}
{{- end }}
{{- end }}
+49
View File
@@ -0,0 +1,49 @@
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "gochat.fullname" . }}
labels:
{{- include "gochat.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "gochat.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
behavior:
scaleDown:
stabilizationWindowSeconds: {{ .Values.autoscaling.behavior.scaleDown.stabilizationWindowSeconds }}
policies:
- type: Percent
value: {{ .Values.autoscaling.behavior.scaleDown.percent }}
periodSeconds: {{ .Values.autoscaling.behavior.scaleDown.periodSeconds }}
scaleUp:
stabilizationWindowSeconds: {{ .Values.autoscaling.behavior.scaleUp.stabilizationWindowSeconds }}
policies:
- type: Percent
value: {{ .Values.autoscaling.behavior.scaleUp.percent }}
periodSeconds: {{ .Values.autoscaling.behavior.scaleUp.periodSeconds }}
- type: Pods
value: {{ .Values.autoscaling.behavior.scaleUp.pods }}
periodSeconds: {{ .Values.autoscaling.behavior.scaleUp.podsPeriodSeconds }}
selectPolicy: Max
{{- end }}
+37
View File
@@ -0,0 +1,37 @@
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "gochat.fullname" . }}
labels:
{{- include "gochat.labels" . | nindent 4 }}
annotations:
{{- toYaml .Values.ingress.annotations | nindent 4 }}
spec:
ingressClassName: {{ .Values.ingress.className }}
{{- 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 "gochat.fullname" $ }}
port:
number: {{ $.Values.app.service.port }}
{{- end }}
{{- end }}
{{- end }}
+83
View File
@@ -0,0 +1,83 @@
{{- if .Values.tracing.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "gochat.fullname" . }}-jaeger
labels:
{{- include "gochat.labels" . | nindent 4 }}
app.kubernetes.io/component: jaeger
spec:
replicas: 1
selector:
matchLabels:
{{- include "gochat.selectorLabels" . | nindent 6 }}
app.kubernetes.io/component: jaeger
template:
metadata:
labels:
{{- include "gochat.selectorLabels" . | nindent 8 }}
app.kubernetes.io/component: jaeger
spec:
containers:
- name: jaeger
image: "{{ .Values.tracing.jaeger.image.repository }}:{{ .Values.tracing.jaeger.image.tag }}"
imagePullPolicy: {{ .Values.tracing.jaeger.image.pullPolicy }}
ports:
- name: otlp-grpc
containerPort: 4317
protocol: TCP
- name: otlp-http
containerPort: 4318
protocol: TCP
- name: jaeger-query
containerPort: 16686
protocol: TCP
- name: jaeger-admin
containerPort: 14269
protocol: TCP
env:
- name: COLLECTOR_OTLP_ENABLED
value: "true"
- name: LOG_LEVEL
value: {{ .Values.tracing.jaeger.logLevel | quote }}
resources:
{{- toYaml .Values.tracing.jaeger.resources | nindent 12 }}
livenessProbe:
httpGet:
path: /
port: 14269
initialDelaySeconds: 5
periodSeconds: 15
readinessProbe:
httpGet:
path: /
port: 14269
initialDelaySeconds: 5
periodSeconds: 15
---
apiVersion: v1
kind: Service
metadata:
name: {{ include "gochat.fullname" . }}-jaeger
labels:
{{- include "gochat.labels" . | nindent 4 }}
app.kubernetes.io/component: jaeger
spec:
type: ClusterIP
ports:
- name: otlp-grpc
port: 4317
targetPort: otlp-grpc
protocol: TCP
- name: otlp-http
port: 4318
targetPort: otlp-http
protocol: TCP
- name: query
port: 16686
targetPort: jaeger-query
protocol: TCP
selector:
{{- include "gochat.selectorLabels" . | nindent 4 }}
app.kubernetes.io/component: jaeger
{{- end }}
@@ -0,0 +1,17 @@
{{- if .Values.metrics.enabled }}
apiVersion: v1
kind: Service
metadata:
name: {{ include "gochat.fullname" . }}-metrics
labels:
{{- include "gochat.labels" . | nindent 4 }}
spec:
type: {{ .Values.metrics.service.type }}
ports:
- port: {{ .Values.metrics.service.port }}
targetPort: metrics
protocol: TCP
name: metrics
selector:
{{- include "gochat.selectorLabels" . | nindent 4 }}
{{- end }}
@@ -0,0 +1,97 @@
{{- if .Values.networkPolicy.enabled }}
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ include "gochat.fullname" . }}-allow-ingress
labels:
{{- include "gochat.labels" . | nindent 4 }}
spec:
podSelector:
matchLabels:
{{- include "gochat.selectorLabels" . | nindent 6 }}
policyTypes:
- Ingress
- Egress
ingress:
# Allow traffic from Ingress controller (nginx)
- from:
- namespaceSelector:
matchLabels:
{{- toYaml .Values.networkPolicy.ingressNamespaceLabels | nindent 12 }}
ports:
- protocol: TCP
port: {{ .Values.app.service.port }}
# Allow Prometheus scraping for metrics
- from:
- namespaceSelector:
matchLabels:
{{- toYaml .Values.networkPolicy.monitoringNamespaceLabels | nindent 12 }}
ports:
- protocol: TCP
port: {{ .Values.metrics.service.port }}
# Allow internal pod-to-pod communication (app ↔ worker)
- from:
- podSelector:
matchLabels:
{{- include "gochat.selectorLabels" . | nindent 12 }}
ports:
- protocol: TCP
port: {{ .Values.app.service.port }}
---
# Egress policy: allow DNS, PostgreSQL, Redis, and outbound HTTPS
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ include "gochat.fullname" . }}-allow-egress
labels:
{{- include "gochat.labels" . | nindent 4 }}
spec:
podSelector:
matchLabels:
{{- include "gochat.selectorLabels" . | nindent 6 }}
policyTypes:
- Egress
egress:
# Allow DNS resolution (kube-dns)
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
- podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
# Allow PostgreSQL connection
- to:
- podSelector:
matchLabels:
app.kubernetes.io/name: postgresql
ports:
- protocol: TCP
port: 5432
# Allow Redis connection
- to:
- podSelector:
matchLabels:
app.kubernetes.io/name: redis
ports:
- protocol: TCP
port: 6379
# Allow outbound HTTPS (LLM APIs, webhook callbacks, SMTP)
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
ports:
- protocol: TCP
port: 443
- protocol: TCP
port: 587
{{- end }}
@@ -0,0 +1,21 @@
{{- if .Values.tracing.enabled }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "gochat.fullname" . }}-otel-config
labels:
{{- include "gochat.labels" . | nindent 4 }}
data:
OTEL_SERVICE_NAME: "{{ include "gochat.fullname" . }}"
OTEL_EXPORTER_OTLP_ENDPOINT: "{{ .Values.tracing.otlp.endpoint }}"
OTEL_EXPORTER_OTLP_PROTOCOL: "{{ .Values.tracing.otlp.protocol }}"
OTEL_TRACES_SAMPLER: "{{ .Values.tracing.sampler.type }}"
OTEL_TRACES_SAMPLER_ARG: "{{ .Values.tracing.sampler.arg }}"
OTEL_PROPAGATORS: "{{ .Values.tracing.propagators }}"
OTEL_RESOURCE_ATTRIBUTES: "service.name={{ include "gochat.fullname" . }},service.version={{ .Values.app.image.tag }},deployment.environment={{ .Values.global.environment }}"
OTEL_LOG_LEVEL: "{{ .Values.tracing.logLevel }}"
OTEL_EXPORTER_OTLP_TIMEOUT: "{{ .Values.tracing.otlp.timeout }}"
OTEL_BSP_SCHEDULE_DELAY: "5000"
OTEL_BSP_MAX_QUEUE_SIZE: "2048"
OTEL_BSP_MAX_EXPORT_BATCH_SIZE: "512"
{{- end }}
+18
View File
@@ -0,0 +1,18 @@
{{- if .Values.podDisruptionBudget.enabled }}
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: {{ include "gochat.fullname" . }}
labels:
{{- include "gochat.labels" . | nindent 4 }}
spec:
{{- if .Values.podDisruptionBudget.minAvailable }}
minAvailable: {{ .Values.podDisruptionBudget.minAvailable }}
{{- end }}
{{- if .Values.podDisruptionBudget.maxUnavailable }}
maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }}
{{- end }}
selector:
matchLabels:
{{- include "gochat.selectorLabels" . | nindent 6 }}
{{- end }}
@@ -0,0 +1,23 @@
{{- if .Values.sealedSecrets.enabled }}
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: {{ include "gochat.fullname" . }}-sealed-secret
labels:
{{- include "gochat.labels" . | nindent 4 }}
annotations:
# Sealed Secrets are encrypted with the cluster's public key
# Use kubeseal to encrypt: kubeseal --format yaml < secret.yaml > sealed-secret.yaml
sealedsecrets.bitnami.com/cluster-wide: "true"
spec:
encryptedData:
{{- range $key, $value := .Values.sealedSecrets.encryptedData }}
{{ $key }}: {{ $value }}
{{- end }}
template:
metadata:
name: {{ include "gochat.fullname" . }}-secret
labels:
{{- include "gochat.labels" . | nindent 8 }}
type: Opaque
{{- end }}
+11
View File
@@ -0,0 +1,11 @@
apiVersion: v1
kind: Secret
metadata:
name: {{ include "gochat.fullname" . }}-secret
labels:
{{- include "gochat.labels" . | nindent 4 }}
type: Opaque
data:
{{- range $key, $value := .Values.secrets.data }}
{{ $key }}: {{ $value | b64enc }}
{{- end }}
+15
View File
@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "gochat.fullname" . }}
labels:
{{- include "gochat.labels" . | nindent 4 }}
spec:
type: {{ .Values.app.service.type }}
ports:
- port: {{ .Values.app.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "gochat.selectorLabels" . | nindent 4 }}
@@ -0,0 +1,16 @@
{{- if and .Values.metrics.enabled .Values.metrics.serviceMonitor.enabled }}
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: {{ include "gochat.fullname" . }}-metrics
labels:
{{- include "gochat.labels" . | nindent 4 }}
spec:
selector:
matchLabels:
{{- include "gochat.selectorLabels" . | nindent 6 }}
endpoints:
- port: metrics
interval: {{ .Values.metrics.serviceMonitor.interval }}
path: {{ .Values.metrics.serviceMonitor.path }}
{{- end }}
@@ -0,0 +1,35 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "gochat.fullname" . }}-worker
labels:
{{- include "gochat.labels" . | nindent 4 }}
app.kubernetes.io/component: worker
spec:
replicas: {{ .Values.worker.replicaCount }}
selector:
matchLabels:
{{- include "gochat.selectorLabels" . | nindent 6 }}
app.kubernetes.io/component: worker
template:
metadata:
labels:
{{- include "gochat.selectorLabels" . | nindent 8 }}
app.kubernetes.io/component: worker
spec:
containers:
- name: worker
image: "{{ .Values.worker.image.repository }}:{{ .Values.worker.image.tag }}"
imagePullPolicy: {{ .Values.worker.image.pullPolicy }}
command: {{- toYaml .Values.worker.command | nindent 12 }}
envFrom:
- configMapRef:
name: {{ include "gochat.fullname" . }}-config
- secretRef:
name: {{ include "gochat.fullname" . }}-secret
resources:
{{- toYaml .Values.worker.resources | nindent 12 }}
{{- with .Values.app.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
+89
View File
@@ -0,0 +1,89 @@
# GoChat Production Values Override
global:
environment: production
app:
replicaCount: 3
resources:
limits:
cpu: 2000m
memory: 1Gi
requests:
cpu: 500m
memory: 512Mi
worker:
replicaCount: 3
resources:
limits:
cpu: 2000m
memory: 1Gi
postgresql:
primary:
persistence:
size: 50Gi
resources:
limits:
cpu: 2000m
memory: 2Gi
redis:
master:
persistence:
size: 10Gi
configuration: |
maxmemory 512mb
maxmemory-policy allkeys-lru
appendonly yes
appendfsync everysec
replica:
replicaCount: 2
ingress:
hosts:
- host: gochat.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: gochat-tls
hosts:
- gochat.example.com
# ---- Autoscaling (HPA) — enabled in production ----
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 15
targetCPUUtilizationPercentage: 70
targetMemoryUtilizationPercentage: 80
# ---- NetworkPolicy — enabled in production ----
networkPolicy:
enabled: true
# ---- PodDisruptionBudget — enabled in production ----
podDisruptionBudget:
enabled: true
minAvailable: 1
# ---- Sealed Secrets — use in production ----
sealedSecrets:
enabled: true
encryptedData: {}
# Generate encrypted data with: kubeseal --format yaml < secret.yaml
# ---- Distributed Tracing — enabled in production ----
tracing:
enabled: true
sampler:
type: parentbased_traceidratio
arg: "0.1" # 10% sampling in production
configMap:
data:
GOCHAT_ENV: "production"
GOCHAT_SERVER_MODE: "release"
GOCHAT_LOG_LEVEL: "info"
GOCHAT_WORKER_CONCURRENCY: "10"
+54
View File
@@ -0,0 +1,54 @@
# GoChat Staging Values Override
global:
environment: staging
app:
replicaCount: 1
image:
tag: "develop"
resources:
limits:
cpu: 500m
memory: 256Mi
requests:
cpu: 250m
memory: 128Mi
worker:
replicaCount: 1
resources:
limits:
cpu: 500m
memory: 256Mi
postgresql:
primary:
persistence:
size: 5Gi
redis:
master:
persistence:
size: 2Gi
configuration: |
maxmemory 128mb
maxmemory-policy allkeys-lru
appendonly yes
ingress:
hosts:
- host: gochat-staging.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: gochat-staging-tls
hosts:
- gochat-staging.example.com
configMap:
data:
GOCHAT_ENV: "staging"
GOCHAT_SERVER_MODE: "debug"
GOCHAT_LOG_LEVEL: "debug"
FRONTEND_URL: "https://gochat-staging.example.com"
+278
View File
@@ -0,0 +1,278 @@
# GoChat Helm Chart Values
# Reference: Chatwoot Helm chart pattern — app + worker + postgres + redis
# Adjust values per environment (dev/staging/prod)
# ---- Global ----
global:
environment: production
# ---- Application ----
app:
replicaCount: 2
image:
repository: gochat/gochat
tag: "1.0.0"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 3000
resources:
limits:
cpu: 1000m
memory: 512Mi
requests:
cpu: 500m
memory: 256Mi
# Health probes — references the health endpoints we created
livenessProbe:
httpGet:
path: /live
port: 3000
initialDelaySeconds: 15
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
# Rolling update strategy
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
# Graceful shutdown configuration
terminationGracePeriodSeconds: 30
preStopDelaySeconds: 5 # Delay before SIGTERM to allow load balancer deregistration
# Environment variables from ConfigMap + Secrets
envFrom:
configMapRef: gochat-config
secretRef: gochat-secret
# Additional env vars
extraEnv: {}
# Pod annotations for monitoring
podAnnotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9090"
prometheus.io/path: "/metrics"
# Affinity for multi-AZ deployment
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values:
- gochat
topologyKey: kubernetes.io/hostname
# Node selector
nodeSelector: {}
# Tolerations
tolerations: []
# ---- Worker ----
worker:
replicaCount: 2
image:
repository: gochat/gochat
tag: "1.0.0"
pullPolicy: IfNotPresent
command: ["serve", "--worker-only"]
resources:
limits:
cpu: 1000m
memory: 512Mi
requests:
cpu: 250m
memory: 256Mi
envFrom:
configMapRef: gochat-config
secretRef: gochat-secret
# ---- Metrics sidecar ----
metrics:
enabled: true
service:
type: ClusterIP
port: 9090
serviceMonitor:
enabled: true
interval: 15s
path: /metrics
# ---- PostgreSQL ----
postgresql:
enabled: true # Set false to use external PostgreSQL
image:
repository: pgvector/pgvector
tag: pg16
auth:
database: gochat_production
username: gochat
password: "" # Set via --set or secrets
existingSecret: gochat-postgres-secret
primary:
persistence:
enabled: true
size: 10Gi
storageClass: ""
resources:
limits:
cpu: 1000m
memory: 1Gi
requests:
cpu: 500m
memory: 512Mi
service:
port: 5432
# ---- Redis ----
redis:
enabled: true # Set false to use external Redis
auth:
password: "" # Set via --set or secrets
existingSecret: gochat-redis-secret
master:
persistence:
enabled: true
size: 5Gi
storageClass: ""
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 250m
memory: 256Mi
configuration: |
maxmemory 512mb
maxmemory-policy allkeys-lru
appendonly yes
appendfsync everysec
replica:
replicaCount: 1
persistence:
enabled: true
size: 5Gi
# ---- Ingress ----
ingress:
enabled: true
className: "nginx"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
hosts:
- host: gochat.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: gochat-tls
hosts:
- gochat.example.com
# ---- ConfigMap data ----
configMap:
data:
GOCHAT_ENV: "production"
GOCHAT_SERVER_HOST: "0.0.0.0"
GOCHAT_SERVER_PORT: "3000"
GOCHAT_SERVER_MODE: "release"
GOCHAT_LOG_LEVEL: "info"
GOCHAT_LOG_FORMAT: "json"
GOCHAT_METRICS_ENABLED: "true"
GOCHAT_METRICS_PORT: "9090"
GOCHAT_WORKER_CONCURRENCY: "10"
GOCHAT_FEATURE_CAPTAIN_AI: "false"
GOCHAT_FEATURE_CSAT: "true"
FRONTEND_URL: "https://gochat.example.com"
# ---- Autoscaling (HPA) ----
autoscaling:
enabled: false # Enable for production
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 70
targetMemoryUtilizationPercentage: 80
behavior:
scaleDown:
stabilizationWindowSeconds: 300
percent: 10
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 60
percent: 50
periodSeconds: 60
pods: 2
podsPeriodSeconds: 60
# ---- NetworkPolicy ----
networkPolicy:
enabled: false # Enable for production
ingressNamespaceLabels:
kubernetes.io/metadata.name: ingress-nginx
monitoringNamespaceLabels:
kubernetes.io/metadata.name: monitoring
# ---- PodDisruptionBudget ----
podDisruptionBudget:
enabled: false # Enable for production (requires >= 2 replicas)
minAvailable: 1 # Keep at least 1 pod available during disruptions
# maxUnavailable: 1 # Alternative: allow max 1 pod unavailable
# ---- Secrets (placeholder — use --set or sealed-secrets) ----
secrets:
data: {}
# POSTGRES_PASSWORD, REDIS_PASSWORD, JWT_SECRET, SMTP_PASSWORD, etc.
# MUST be set via --set or external secret management
# ---- Sealed Secrets / External Secret Management ----
sealedSecrets:
enabled: false # Enable for production
encryptedData: {}
externalSecret:
enabled: false # Enable for production with External Secrets Operator
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
mapping: {}
# POSTGRES_PASSWORD: postgres-password
# REDIS_PASSWORD: redis-password
# JWT_SECRET: jwt-secret
# ---- Distributed Tracing (OpenTelemetry + Jaeger) ----
tracing:
enabled: false # Enable for production/staging
sampler:
type: parentbased_traceidratio # Sampling strategy: always_on, always_off, parentbased_traceidratio
arg: "0.1" # Sample 10% of traces in production (adjust per environment)
propagators: "tracecontext,baggage" # W3C Trace Context propagation
logLevel: info
otlp:
endpoint: "gochat-jaeger:4317" # OTLP gRPC endpoint (in-cluster Jaeger)
protocol: grpc
timeout: "10s"
jaeger:
image:
repository: jaegertracing/all-in-one
tag: "1.55"
pullPolicy: IfNotPresent
logLevel: info
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 128Mi