在 Kubernetes 部署
架构概述
DataAgent 在 K8s 环境部署时需要注意以下有状态组件:
关键点:/app/nocobase/storage 必须使用 PVC 持久化存储,否则 Pod 重启会导致上传文件等数据丢失; 如果要求镜像不允许写入文件系统,那么/app/nocobase 目录本身应挂载到 emptyDir,不然可以不挂载。
部署配置
1. 存储声明 (PVC)
创建支持多 Pod 共享读写的存储卷:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: yiask-storage
namespace: default
spec:
accessModes:
- ReadWriteMany # 多 Pod 共享读写
resources:
requests:
storage: 500Gi # 生产环境建议 500Gi 及以上,并根据实际需求调整
# storageClassName: nfs # 使用支持 RWX 的 StorageClass
生产环境建议 PVC 容量配置为 500Gi 及以上,并结合业务数据量、日志增长速度和备份保留周期预留扩容空间。
StorageClass 选择:
- NFS:适合小规模部署
- Ceph Rook:适合生产环境,高可用
- 云存储:AWS EFS、阿里云 NAS 等
2. 部署 (Deployment)
apiVersion: apps/v1
kind: Deployment
metadata:
name: yiask
namespace: default
labels:
app: yiask
spec:
replicas: 2 # 根据负载调整
selector:
matchLabels:
app: yiask
template:
metadata:
labels:
app: yiask
spec:
volumes:
- name: nocobase-root
emptyDir: {} # 2.0.6 起 /app/nocobase 根目录使用临时可写卷
- name: storage
persistentVolumeClaim:
claimName: yiask-storage
containers:
- name: yiask
image: your-registry/yiask/dataagent:latest
ports:
- containerPort: 13000
env:
# ===== 必需环境变量 =====
- name: APP_ENV
value: production
- name: APP_KEY
valueFrom:
secretKeyRef:
name: yiask-secrets
key: app-key
- name: DB_DIALECT
value: postgres
- name: DB_HOST
value: postgres-service # 替换为实际数据库地址
- name: DB_PORT
value: '5432'
- name: DB_DATABASE
valueFrom:
secretKeyRef:
name: yiask-secrets
key: db-database
- name: DB_USER
valueFrom:
secretKeyRef:
name: yiask-secrets
key: db-user
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: yiask-secrets
key: db-password
# ===== 集群模式配置(K8s 环境必需)=====
- name: CLUSTER_MODE
value: '1'
# 说明:K8s 环境中多进程能力由 Pod 副本提供,因此 CLUSTER_MODE 建议设为 1
# ===== 缓存配置(多 Pod 必需使用 Redis)=====
- name: CACHE_DEFAULT_STORE
value: redis
# 方案 A:单机 / Service 暴露的 Redis
- name: REDIS_URL
value: redis://redis-service:6379/0
# 方案 B:外部 Redis Sentinel(二选一;启用时删除上面的 REDIS_URL)
# - name: REDIS_MODE
# value: sentinel
# - name: REDIS_SENTINEL_NAME
# value: mymaster
# - name: REDIS_SENTINEL_NODES
# value: redis-sentinel-0:26379,redis-sentinel-1:26379,redis-sentinel-2:26379
# - name: REDIS_USERNAME
# valueFrom:
# secretKeyRef:
# name: yiask-secrets
# key: redis-username
# - name: REDIS_PASSWORD
# valueFrom:
# secretKeyRef:
# name: yiask-secrets
# key: redis-password
# - name: REDIS_DB
# value: "0"
# ===== 日志配置(输出到 stdout,配合 K8s 日志采集)=====
- name: LOGGER_TRANSPORT
value: console,dailyRotateFile
- name: LOGGER_LEVEL
value: info
volumeMounts:
- name: storage
mountPath: /app/nocobase/storage # 先挂载子目录 PVC
- name: nocobase-root
mountPath: /app/nocobase # 再挂载根目录 emptyDir
resources:
requests:
memory: '1Gi'
cpu: '500m'
limits:
memory: '4Gi'
cpu: '2000m'
livenessProbe:
httpGet:
path: /api/v1/__health_check
port: 13000
initialDelaySeconds: 60
periodSeconds: 10
readinessProbe:
httpGet:
path: /api/v1/__health_check
port: 13000
initialDelaySeconds: 30
periodSeconds: 5
3. Service
apiVersion: v1
kind: Service
metadata:
name: yiask-service
namespace: default
spec:
selector:
app: yiask
ports:
- port: 80
targetPort: 13000
type: ClusterIP
4. Ingress
注意:DataAgent 使用 SSE (Server-Sent Events) 进行流式响应,需要增加超时时间。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: yiask-ingress
namespace: default
annotations:
nginx.ingress.kubernetes.io/proxy-connect-timeout: '3600'
nginx.ingress.kubernetes.io/proxy-send-timeout: '3600'
nginx.ingress.kubernetes.io/proxy-read-timeout: '3600'
nginx.ingress.kubernetes.io/proxy-buffer-size: '16k'
nginx.ingress.kubernetes.io/ssl-redirect: 'true'
spec:
ingressClassName: nginx
tls:
- hosts:
- yiask.example.com
secretName: yiask-tls
rules:
- host: yiask.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: yiask-service
port:
number: 80
5. Secret
apiVersion: v1
kind: Secret
metadata:
name: yiask-secrets
namespace: default
type: Opaque
stringData:
app-key: 'your-random-app-key-min-32-chars'
db-database: 'yiask'
db-user: 'yiask'
db-password: 'your-db-password'
会话亲和性
为了优化性能,可以配置同一会话的请求路由到同一个 Pod:
# 在 Service 中添加
spec:
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 3600 # 1 小时
说明:这不是必需的,因为 PVC 已经实现了数据共享。但配置会话亲和性可以减少跨 Pod 访问文件的开销。
依赖服务
PostgreSQL (必需)
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:17
env:
- name: POSTGRES_DB
value: yiask
- name: POSTGRES_USER
value: yiask
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: yiask-secrets
key: db-password
volumeMounts:
- name: postgres-data
mountPath: /var/lib/postgresql/data
volumes:
- name: postgres-data
persistentVolumeClaim:
claimName: postgres-storage
---
apiVersion: v1
kind: Service
metadata:
name: postgres-service
spec:
selector:
app: postgres
ports:
- port: 5432
Redis (必需)
用于多 Pod 间缓存共享,必需。
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7-alpine
ports:
- containerPort: 6379
---
apiVersion: v1
kind: Service
metadata:
name: redis-service
spec:
selector:
app: redis
ports:
- port: 6379
如果使用外部 Redis Sentinel,而不是在当前集群内自行部署单节点 Redis,则可以跳过本节的 redis Deployment / Service,改为在应用 Deployment 中配置 REDIS_MODE=sentinel、REDIS_SENTINEL_NAME 和 REDIS_SENTINEL_NODES 等环境变量。变量说明见 环境变量。
只读文件系统的容器
部分客户的生产环境会将容器文件系统设置为只读。对于 2.0.6 及以上版本,建议按下面方式拆分挂载:
注意:/app/nocobase/storage 和 /app/nocobase 是父子目录,配置 volumeMounts 时必须先挂载 /app/nocobase/storage,再挂载 /app/nocobase,否则子目录挂载可能被父目录覆盖。新版本中 PM2 运行时已经位于 /app/nocobase 下,不需要再单独配置 pm2-home。
1. 映射 /app/nocobase/storage 到 PVC
将 /app/nocobase/storage 继续挂载到原有 PVC,可持久化上传文件、日志等业务数据:
volumes:
- name: storage
persistentVolumeClaim:
claimName: yiask-storage
---
volumeMounts:
- name: storage
mountPath: /app/nocobase/storage
2. 映射 /app/nocobase 到 emptyDir
将 /app/nocobase 根目录挂载到 emptyDir,用于存储应用启动和运行过程中的临时可写文件:
volumes:
- name: nocobase-root
emptyDir: {}
---
volumeMounts:
- name: nocobase-root
mountPath: /app/nocobase
完整配置示例
spec:
volumes:
- name: storage
persistentVolumeClaim:
claimName: yiask-storage
- name: nocobase-root
emptyDir: {}
containers:
- name: yiask
# ... 其他配置
volumeMounts:
- name: storage
mountPath: /app/nocobase/storage
- name: nocobase-root
mountPath: /app/nocobase
故障排查
Pod 无法启动
-
检查 PVC 是否已绑定:
kubectl get pvc
kubectl describe pvc yiask-storage
-
检查存储类是否支持 ReadWriteMany
Pod 报错 Pod ephemeral local storage usage exceeds the total limit of containers 1Gi
这个报错通常表示容器可用的临时本地存储(ephemeral local storage)不够。DataAgent 在启动时会先解压运行所需的代码和运行时文件,如果 /app/nocobase 落在容器的临时本地存储上,而 ephemeral-storage 限制又比较小,就可能在启动阶段直接触发这个错误。
可以按下面两种方式处理:
-
提升容器的 ephemeral-storage 限制,建议至少调到 5Gi:
resources:
requests:
memory: '1Gi'
cpu: '500m'
ephemeral-storage: '1Gi'
limits:
memory: '4Gi'
cpu: '2000m'
ephemeral-storage: '5Gi'
-
将 /app/nocobase 直接挂载到 PVC,避免解压后的代码和运行文件继续占用容器本地临时存储。
这种方式下,/app/nocobase/storage 不需要再单独挂载到 PVC,因为它已经包含在 /app/nocobase 里面了;但是 /app/nocobase/runtime 仍然建议单独挂载到 emptyDir,用于保存运行时临时文件。
注意:/app/nocobase/runtime 和 /app/nocobase 是父子目录,配置 volumeMounts 时必须先挂载 /app/nocobase/runtime,再挂载 /app/nocobase,保证子目录挂载优先级更高,不会被父目录覆盖。
spec:
volumes:
- name: nocobase-runtime
emptyDir: {}
- name: nocobase-data
persistentVolumeClaim:
claimName: yiask-storage
containers:
- name: yiask
# ... 其他配置
volumeMounts:
- name: nocobase-runtime
mountPath: /app/nocobase/runtime
- name: nocobase-data
mountPath: /app/nocobase
如果当前环境已经有可用 PVC,优先建议采用第二种方式。这样既能绕开临时存储容量不足的问题,也能减少启动阶段因为解压导致的本地磁盘占用。
文件丢失
- 确认
/app/nocobase 已挂载到 emptyDir,/app/nocobase/storage 已挂载到 PVC
- 检查 Pod 重启后文件是否存在
SSE 连接中断
- 检查 Ingress timeout 配置
- 检查负载均衡器(如 AWS ALB)的 idle timeout 设置