feat: add direct PostgreSQL and ACK deployment support

This commit is contained in:
2026-08-12 19:12:00 +08:00
parent d0192081c7
commit c44274f098
55 changed files with 3317 additions and 1064 deletions

18
deploy/ack/configmap.yaml Normal file
View File

@@ -0,0 +1,18 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: zhinian-runtime
namespace: zhinian
data:
NODE_ENV: production
PORT: "3000"
ZHINIAN_DATA_BACKEND: postgres
ZHINIAN_AUTH_REQUIRED: auto
ZHINIAN_WORKER_BASE_URL: http://zhinian-web:3000
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-web

33
deploy/ack/ingress.yaml Normal file
View File

@@ -0,0 +1,33 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: zhinian-web
namespace: zhinian
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: 100m
spec:
ingressClassName: nginx
rules:
- host: REPLACE_WITH_PUBLIC_HOST
http:
paths:
# Longest-prefix matching sends public internal-API traffic to the
# selectorless deny Service instead of the Web workload.
- path: /api/internal/worker
pathType: Prefix
backend:
service:
name: zhinian-public-deny
port:
number: 80
- path: /
pathType: Prefix
backend:
service:
name: zhinian-web
port:
number: 3000
tls:
- hosts:
- REPLACE_WITH_PUBLIC_HOST
secretName: REPLACE_WITH_TLS_SECRET

View File

@@ -0,0 +1,64 @@
apiVersion: batch/v1
kind: Job
metadata:
name: zhinian-db-migrate
namespace: zhinian
spec:
backoffLimit: 2
ttlSecondsAfterFinished: 86400
template:
metadata:
labels:
app.kubernetes.io/name: zhinian
app.kubernetes.io/component: database-migration
spec:
restartPolicy: Never
automountServiceAccountToken: false
securityContext:
seccompProfile:
type: RuntimeDefault
containers:
- name: migrate
image: REGISTRY/PROJECT/zhinian-aigc:REPLACE_TAG
imagePullPolicy: IfNotPresent
command: ["node", "scripts/migrate-postgres.mjs"]
env:
- name: NODE_ENV
value: production
- name: ZHINIAN_DATA_BACKEND
value: postgres
# Must match the username in zhinian-web-db/DATABASE_URL.
- name: DATABASE_APP_ROLE
value: REPLACE_WITH_RDS_APP_ROLE
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: zhinian-migration-db
key: DATABASE_URL
- name: DATABASE_SSL_MODE
value: verify-full
- name: DATABASE_CA_CERT_PATH
value: /etc/zhinian/rds/ca.pem
- name: DATABASE_CONNECTION_TIMEOUT_MS
value: "5000"
- name: DATABASE_STATEMENT_TIMEOUT_MS
value: "60000"
volumeMounts:
- name: rds-ca
mountPath: /etc/zhinian/rds
readOnly: true
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
volumes:
- name: rds-ca
secret:
secretName: zhinian-rds-ca

View File

@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: zhinian

View File

@@ -0,0 +1,36 @@
# Example only. Replace every placeholder and keep the populated file out of Git.
apiVersion: v1
kind: Secret
metadata:
name: zhinian-web-db
namespace: zhinian
type: Opaque
stringData:
DATABASE_URL: postgresql://APP_USER:APP_PASSWORD@RDS_INTERNAL_HOST:5432/APP_DATABASE
---
apiVersion: v1
kind: Secret
metadata:
name: zhinian-migration-db
namespace: zhinian
type: Opaque
stringData:
DATABASE_URL: postgresql://MIGRATION_USER:MIGRATION_PASSWORD@RDS_INTERNAL_HOST:5432/APP_DATABASE
---
apiVersion: v1
kind: Secret
metadata:
name: zhinian-worker-auth
namespace: zhinian
type: Opaque
stringData:
ZHINIAN_INTERNAL_WORKER_TOKEN: REPLACE_WITH_A_LONG_RANDOM_VALUE
---
apiVersion: v1
kind: Secret
metadata:
name: zhinian-web-auth
namespace: zhinian
type: Opaque
stringData:
ZHINIAN_AUTH_SESSION_SECRET: REPLACE_WITH_A_DIFFERENT_LONG_RANDOM_VALUE

28
deploy/ack/service.yaml Normal file
View File

@@ -0,0 +1,28 @@
apiVersion: v1
kind: Service
metadata:
name: zhinian-web
namespace: zhinian
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: zhinian
app.kubernetes.io/component: web
ports:
- name: http
port: 3000
targetPort: 3000
---
# Deliberately selectorless: the public Ingress routes internal API paths here,
# where there are no endpoints, instead of forwarding them to Web.
apiVersion: v1
kind: Service
metadata:
name: zhinian-public-deny
namespace: zhinian
spec:
type: ClusterIP
ports:
- name: deny
port: 80
targetPort: 8080

95
deploy/ack/web.yaml Normal file
View File

@@ -0,0 +1,95 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: zhinian-web
namespace: zhinian
spec:
# Keep one replica until uploaded/generated files are stored in OSS or another
# shared object store. PostgreSQL alone does not make local runtime files shared.
replicas: 1
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # Budget RDS connections for (replicas + maxSurge) * DATABASE_POOL_MAX.
maxUnavailable: 0
selector:
matchLabels:
app.kubernetes.io/name: zhinian
app.kubernetes.io/component: web
template:
metadata:
labels:
app.kubernetes.io/name: zhinian
app.kubernetes.io/component: web
spec:
automountServiceAccountToken: false
securityContext:
seccompProfile:
type: RuntimeDefault
containers:
- name: web
image: REGISTRY/PROJECT/zhinian-aigc:REPLACE_TAG
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 3000
envFrom:
- 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
- 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
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: 512Mi
limits:
cpu: "1"
memory: 1Gi
securityContext:
allowPrivilegeEscalation: false
capabilities:
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

49
deploy/ack/worker.yaml Normal file
View File

@@ -0,0 +1,49 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: zhinian-worker
namespace: zhinian
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: zhinian
app.kubernetes.io/component: worker
template:
metadata:
labels:
app.kubernetes.io/name: zhinian
app.kubernetes.io/component: worker
spec:
automountServiceAccountToken: false
securityContext:
seccompProfile:
type: RuntimeDefault
containers:
- name: worker
image: REGISTRY/PROJECT/zhinian-aigc:REPLACE_TAG
imagePullPolicy: IfNotPresent
command: ["node", "scripts/worker.mjs"]
env:
- name: NODE_ENV
value: production
- name: ZHINIAN_WORKER_BASE_URL
value: http://zhinian-web:3000
- name: ZHINIAN_WORKER_REQUEST_TIMEOUT_MS
value: "120000"
- name: ZHINIAN_INTERNAL_WORKER_TOKEN
valueFrom:
secretKeyRef:
name: zhinian-worker-auth
key: ZHINIAN_INTERNAL_WORKER_TOKEN
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]