feat: add Go workload deployment artifacts and split Ingress routing

This commit is contained in:
2026-08-14 09:50:47 +08:00
parent ca019abb14
commit 4a8f2d56e2
13 changed files with 452 additions and 26 deletions

View File

@@ -16,3 +16,31 @@ data:
DATABASE_IDLE_TIMEOUT_MS: "30000"
DATABASE_STATEMENT_TIMEOUT_MS: "30000"
DATABASE_APPLICATION_NAME: zhinian-web
---
# Go API runtime settings. Provider endpoints/models use code defaults unless
# overridden here; credentials always come from zhinian-go-providers.
apiVersion: v1
kind: ConfigMap
metadata:
name: zhinian-go-runtime
namespace: zhinian
data:
NODE_ENV: production
GO_BACKEND_PORT: "8080"
ZHINIAN_DATA_BACKEND: postgres
ZHINIAN_AUTH_REQUIRED: "true"
ZHINIAN_AUTH_COOKIE_SECURE: "true"
ZHINIAN_PUBLIC_BASE_URL: https://REPLACE_WITH_PUBLIC_HOST
DATABASE_SSL_MODE: verify-full
DATABASE_CA_CERT_PATH: /etc/zhinian/rds/ca.pem
DATABASE_POOL_MAX: "10"
DATABASE_CONNECTION_TIMEOUT_MS: "5000"
DATABASE_IDLE_TIMEOUT_MS: "30000"
DATABASE_STATEMENT_TIMEOUT_MS: "30000"
DATABASE_APPLICATION_NAME: zhinian-go-api
ZHINIAN_GO_EMBEDDED_WORKER: "true"
ZHINIAN_WORKER_ID: zhinian-go-api-embedded
ZHINIAN_BILLING_REQUIRED: "1"
ZHINIAN_RUNTIME_DIR: /var/lib/zhinian/runtime
ZHINIAN_LOG_DIR: /var/lib/zhinian/logs
ZHINIAN_SETTINGS_FILE: /var/lib/zhinian/settings.env

207
deploy/ack/go-api.yaml Normal file
View File

@@ -0,0 +1,207 @@
# Go API workload: owns /api, /uploads, and /generated-results from the first
# production deployment. The embedded WorkerLoop replaces the Node Worker; do
# not deploy zhinian-worker in production.
apiVersion: apps/v1
kind: Deployment
metadata:
name: zhinian-go-api
namespace: zhinian
spec:
# Keep one replica until generated assets live in shared OSS storage and the
# RDS/provider connection budget is measured for more.
replicas: 1
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app.kubernetes.io/name: zhinian
app.kubernetes.io/component: go-api
template:
metadata:
labels:
app.kubernetes.io/name: zhinian
app.kubernetes.io/component: go-api
spec:
automountServiceAccountToken: false
securityContext:
seccompProfile:
type: RuntimeDefault
containers:
- name: go-api
image: REGISTRY/PROJECT/zhinian-go-api:REPLACE_TAG
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8080
envFrom:
- configMapRef:
name: zhinian-go-runtime
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: zhinian-go-db
key: DATABASE_URL
# The session secret must be the same value Next.js uses so the
# frontend middleware and the Go backend verify the same cookies.
- name: ZHINIAN_AUTH_SESSION_SECRET
valueFrom:
secretKeyRef:
name: zhinian-web-auth
key: ZHINIAN_AUTH_SESSION_SECRET
- name: ZHINIAN_BOOTSTRAP_ADMIN_PHONE
valueFrom:
secretKeyRef:
name: zhinian-go-bootstrap
key: ZHINIAN_BOOTSTRAP_ADMIN_PHONE
- name: ZHINIAN_BOOTSTRAP_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: zhinian-go-bootstrap
key: ZHINIAN_BOOTSTRAP_ADMIN_PASSWORD
- name: ZHINIAN_BOOTSTRAP_ADMIN_NAME
valueFrom:
secretKeyRef:
name: zhinian-go-bootstrap
key: ZHINIAN_BOOTSTRAP_ADMIN_NAME
optional: true
- name: VOLCENGINE_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: zhinian-go-providers
key: VOLCENGINE_ACCESS_KEY_ID
optional: true
- name: VOLCENGINE_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: zhinian-go-providers
key: VOLCENGINE_SECRET_ACCESS_KEY
optional: true
- name: JIMENG_IMAGE_GENERATE_46_REQ_KEY
valueFrom:
secretKeyRef:
name: zhinian-go-providers
key: JIMENG_IMAGE_GENERATE_46_REQ_KEY
optional: true
- name: EVOLINK_API_KEY
valueFrom:
secretKeyRef:
name: zhinian-go-providers
key: EVOLINK_API_KEY
optional: true
- name: SEEDANCE_API_KEY
valueFrom:
secretKeyRef:
name: zhinian-go-providers
key: SEEDANCE_API_KEY
optional: true
- name: BAILIAN_API_KEY
valueFrom:
secretKeyRef:
name: zhinian-go-providers
key: BAILIAN_API_KEY
optional: true
- name: DASHSCOPE_API_KEY
valueFrom:
secretKeyRef:
name: zhinian-go-providers
key: DASHSCOPE_API_KEY
optional: true
- name: ALI_OSS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: zhinian-go-providers
key: ALI_OSS_ACCESS_KEY_ID
optional: true
- name: ALI_OSS_ACCESS_KEY_SECRET
valueFrom:
secretKeyRef:
name: zhinian-go-providers
key: ALI_OSS_ACCESS_KEY_SECRET
optional: true
- name: ZHINIAN_WEBHOOK_SECRET
valueFrom:
secretKeyRef:
name: zhinian-go-secrets
key: ZHINIAN_WEBHOOK_SECRET
- name: ZHINIAN_API_KEYS
valueFrom:
secretKeyRef:
name: zhinian-go-secrets
key: ZHINIAN_API_KEYS
optional: true
- name: ZHINIAN_INTERNAL_WORKER_TOKEN
valueFrom:
secretKeyRef:
name: zhinian-go-secrets
key: ZHINIAN_INTERNAL_WORKER_TOKEN
optional: true
volumeMounts:
- name: rds-ca
mountPath: /etc/zhinian/rds
readOnly: true
- name: data
mountPath: /var/lib/zhinian
- name: tmp
mountPath: /tmp
startupProbe:
httpGet:
path: /api/health
port: http
periodSeconds: 5
failureThreshold: 24
readinessProbe:
httpGet:
path: /api/ready
port: http
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
livenessProbe:
httpGet:
path: /api/health
port: http
periodSeconds: 20
timeoutSeconds: 3
failureThreshold: 3
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: "1"
memory: 512Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
readOnlyRootFilesystem: true
volumes:
- name: rds-ca
secret:
secretName: zhinian-rds-ca
- name: data
emptyDir: {}
- name: tmp
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: zhinian-go-api
namespace: zhinian
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: zhinian
app.kubernetes.io/component: go-api
ports:
- name: http
port: 8080
targetPort: 8080

View File

@@ -12,7 +12,7 @@ spec:
http:
paths:
# Longest-prefix matching sends public internal-API traffic to the
# selectorless deny Service instead of the Web workload.
# selectorless deny Service instead of any workload.
- path: /api/internal/worker
pathType: Prefix
backend:
@@ -20,6 +20,30 @@ spec:
name: zhinian-public-deny
port:
number: 80
# Backend paths belong to the Go API workload from the first
# production deployment.
- path: /api
pathType: Prefix
backend:
service:
name: zhinian-go-api
port:
number: 8080
- path: /uploads
pathType: Prefix
backend:
service:
name: zhinian-go-api
port:
number: 8080
- path: /generated-results
pathType: Prefix
backend:
service:
name: zhinian-go-api
port:
number: 8080
# Pages and static assets stay with Next.js.
- path: /
pathType: Prefix
backend:

View File

@@ -1,4 +1,6 @@
# Example only. Replace every placeholder and keep the populated file out of Git.
# Secrets marked "(local development only)" are not referenced by the first
# production deployment; keep or drop them as your local workflow requires.
apiVersion: v1
kind: Secret
metadata:
@@ -6,6 +8,7 @@ metadata:
namespace: zhinian
type: Opaque
stringData:
# Local development only: the production Web workload holds no RDS credentials.
DATABASE_URL: postgresql://APP_USER:APP_PASSWORD@RDS_INTERNAL_HOST:5432/APP_DATABASE
---
apiVersion: v1
@@ -15,6 +18,7 @@ metadata:
namespace: zhinian
type: Opaque
stringData:
# Manual schema execution only: run database/migrations/*.sql with this role.
DATABASE_URL: postgresql://MIGRATION_USER:MIGRATION_PASSWORD@RDS_INTERNAL_HOST:5432/APP_DATABASE
---
apiVersion: v1
@@ -24,6 +28,7 @@ metadata:
namespace: zhinian
type: Opaque
stringData:
# Local development only: the Node Worker is not deployed in production.
ZHINIAN_INTERNAL_WORKER_TOKEN: REPLACE_WITH_A_LONG_RANDOM_VALUE
---
apiVersion: v1
@@ -33,4 +38,60 @@ metadata:
namespace: zhinian
type: Opaque
stringData:
# Shared by the Next.js middleware and the Go backend so both verify the
# same session cookies. Keep identical across workloads.
ZHINIAN_AUTH_SESSION_SECRET: REPLACE_WITH_A_DIFFERENT_LONG_RANDOM_VALUE
---
apiVersion: v1
kind: Secret
metadata:
name: zhinian-go-db
namespace: zhinian
type: Opaque
stringData:
# Application role (least privilege): grants applied manually after the SQL.
DATABASE_URL: postgresql://APP_USER:APP_PASSWORD@RDS_INTERNAL_HOST:5432/APP_DATABASE
---
apiVersion: v1
kind: Secret
metadata:
name: zhinian-go-bootstrap
namespace: zhinian
type: Opaque
stringData:
# First super administrator, created once at Go startup when no super
# administrator exists. Password must be at least 8 characters.
ZHINIAN_BOOTSTRAP_ADMIN_PHONE: REPLACE_WITH_ADMIN_PHONE
ZHINIAN_BOOTSTRAP_ADMIN_PASSWORD: REPLACE_WITH_STRONG_PASSWORD
ZHINIAN_BOOTSTRAP_ADMIN_NAME: 平台超级管理员
---
apiVersion: v1
kind: Secret
metadata:
name: zhinian-go-providers
namespace: zhinian
type: Opaque
stringData:
# Provider credentials. Delete keys for providers you do not use; the Go
# workload tolerates missing optional keys and fails closed when an enabled
# engine has no credentials.
VOLCENGINE_ACCESS_KEY_ID: REPLACE_OR_REMOVE
VOLCENGINE_SECRET_ACCESS_KEY: REPLACE_OR_REMOVE
JIMENG_IMAGE_GENERATE_46_REQ_KEY: REPLACE_OR_REMOVE
EVOLINK_API_KEY: REPLACE_OR_REMOVE
SEEDANCE_API_KEY: REPLACE_OR_REMOVE
BAILIAN_API_KEY: REPLACE_OR_REMOVE
DASHSCOPE_API_KEY: REPLACE_OR_REMOVE
ALI_OSS_ACCESS_KEY_ID: REPLACE_OR_REMOVE
ALI_OSS_ACCESS_KEY_SECRET: REPLACE_OR_REMOVE
---
apiVersion: v1
kind: Secret
metadata:
name: zhinian-go-secrets
namespace: zhinian
type: Opaque
stringData:
ZHINIAN_WEBHOOK_SECRET: REPLACE_WITH_A_LONG_RANDOM_VALUE
ZHINIAN_API_KEYS: REPLACE_WITH_PUBLIC_API_KEYS
ZHINIAN_INTERNAL_WORKER_TOKEN: REPLACE_OR_REMOVE

View File

@@ -1,3 +1,8 @@
# Next.js frontend workload for the first production deployment: serves pages,
# static assets, and SSR only. All /api, /uploads, and /generated-results
# traffic is routed to zhinian-go-api by the Ingress, so this workload holds no
# RDS credentials and needs only the shared session secret for local cookie
# verification in the middleware.
apiVersion: apps/v1
kind: Deployment
metadata:
@@ -10,7 +15,7 @@ spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # Budget RDS connections for (replicas + maxSurge) * DATABASE_POOL_MAX.
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
@@ -37,25 +42,13 @@ spec:
- configMapRef:
name: zhinian-runtime
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: zhinian-web-db
key: DATABASE_URL
- name: ZHINIAN_INTERNAL_WORKER_TOKEN
valueFrom:
secretKeyRef:
name: zhinian-worker-auth
key: ZHINIAN_INTERNAL_WORKER_TOKEN
# The session secret must be the same value the Go backend uses so
# the frontend middleware and the Go backend verify the same cookies.
- name: ZHINIAN_AUTH_SESSION_SECRET
valueFrom:
secretKeyRef:
name: zhinian-web-auth
key: ZHINIAN_AUTH_SESSION_SECRET
volumeMounts:
- name: rds-ca
mountPath: /etc/zhinian/rds
readOnly: true
startupProbe:
httpGet:
path: /api/health
@@ -64,7 +57,7 @@ spec:
failureThreshold: 24
readinessProbe:
httpGet:
path: /api/ready
path: /api/health
port: http
periodSeconds: 10
timeoutSeconds: 5
@@ -89,7 +82,3 @@ spec:
drop: ["ALL"]
# The current image runs as root. Add a fixed non-root image user and
# verify /app/.runtime permissions before enabling runAsNonRoot.
volumes:
- name: rds-ca
secret:
secretName: zhinian-rds-ca

View File

@@ -1,3 +1,6 @@
# DEPRECATED (2026-08-14): production deploys the Go API workload with the
# embedded WorkerLoop (ZHINIAN_GO_EMBEDDED_WORKER=true). The Node Worker is
# local-development only; do not apply this manifest in production.
apiVersion: apps/v1
kind: Deployment
metadata: