commit 821ffe9e7023ade7e8b4f226388a2f252b7b6fec Author: chengcheng Date: Thu Jul 9 19:20:28 2026 +0800 初始化提交 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9f11b75 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..7c11bfe --- /dev/null +++ b/README.md @@ -0,0 +1,421 @@ +# EasyAI Kubernetes 部署指南 + +本仓库用于将 EasyAI 部署到客户自己的 Kubernetes 环境中。客户拿到仓库后,只需要按本文准备外部依赖、修改生产配置并执行 `kubectl apply`,即可完成部署。 + +生产部署只使用以下目录: + +```text +k8s/overlays/production +``` + +本地部署验证可参考: + +```text +k8s/overlays/local/README.md +``` + +镜像版本默认使用 `latest`。EasyAI 发布新版本后,客户拉取本仓库最新内容并按“更新到最新版本”执行即可升级。 + +## 部署内容 + +| 服务 | Kubernetes 资源 | 说明 | +| --- | --- | --- | +| 前端 | `easyai-web` | Web 页面入口 | +| 主服务 | `easyai-server` | API、核心业务服务 | +| WebSocket 网关 | `ws-gateway` | `/socket.io` 长连接入口 | +| 视频编辑 | `video-edit` | 视频编辑能力 | +| 沙箱 | `sandbox` | 代码执行/Notebook 沙箱 | +| Agent 服务治理 | `easyai-asg` | Agent 治理服务 | +| Agent 记忆服务 | `agent-memory` | Agent 记忆与检索服务 | + +生产环境建议将 Redis、MongoDB、RabbitMQ、PostgreSQL 等基础组件使用云厂商托管服务,不在本仓库中部署。 + +## 部署前准备 + +### Kubernetes 集群 + +请先确认集群满足以下条件: + +| 项目 | 要求 | +| --- | --- | +| Kubernetes | 可正常使用 `kubectl` 连接 | +| Worker 节点 | 建议不少于 3 个节点,生产环境建议跨可用区 | +| Metrics Server | 已启用,用于 HPA 自动扩缩容 | +| Ingress Controller | 已安装,当前配置默认使用 NGINX Ingress | +| 共享存储 | 有支持 `ReadWriteMany` 的 StorageClass | +| 镜像拉取 | 节点可以访问 `registry.cn-shanghai.aliyuncs.com` | + +检查命令: + +```bash +kubectl config current-context +kubectl get nodes -o wide +kubectl top nodes +kubectl get ingressclass +kubectl get storageclass +``` + +如果 `kubectl top nodes` 不可用,请先启用 Metrics Server。 + +### 外部依赖 + +部署前需要准备以下云服务,并保证 Kubernetes 节点所在 VPC 可以访问: + +以下版本为当前生产部署推荐版本。使用云厂商托管服务时,请固定主版本;大版本升级前先在测试环境完成回归验证。 + +| 依赖 | 推荐版本 | 用途 | 需要准备的信息 | +| --- | --- | --- | --- | +| MongoDB | `7.x` | 主服务业务数据 | 内网地址、端口、用户名、密码、连接参数 | +| Redis | `7.x`,需支持至少 `16` 个逻辑 DB | 队列、缓存、WebSocket 集群状态 | 内网地址、端口、密码,需支持多个逻辑 DB | +| RabbitMQ | `3.13.x`,需支持 AMQP `0-9-1` | 消息队列 | AMQP 地址、端口、用户名、密码、vhost | +| PostgreSQL + pgvector | PostgreSQL `18` + pgvector `0.8.2` | Agent 治理和记忆服务 | 地址、端口、用户名、密码、数据库、扩展 | +| RWX 共享存储 | 与 Kubernetes 集群版本兼容的 CSI/存储插件 | 上传、备份、恢复文件共享 | 支持 `ReadWriteMany` 的 StorageClass | +| OSS/S3 对象存储 | 兼容 AWS S3 Signature V4 或云厂商 OSS/S3 当前 API,可选 | 文件存储,可选 | Endpoint、Bucket、Access Key | + +本地验证环境中的 PostgreSQL/pgvector 镜像为: + +```text +registry.cn-shanghai.aliyuncs.com/easyaigc/pgvector:0.8.2-pg18-trixie +``` + +外部依赖版本核对示例: + +```bash +mongosh --eval 'db.version()' +redis-cli INFO server | grep '^redis_version:' +rabbitmqctl status | grep '{rabbit,' +psql "$ASG_DATABASE_URL" -c 'SHOW server_version;' +psql "$ASG_DATABASE_URL" -c "SELECT extversion FROM pg_extension WHERE extname = 'vector';" +kubectl get storageclass +``` + +PostgreSQL 需要创建两个数据库,并启用扩展: + +```sql +CREATE DATABASE agent_governance; +\c agent_governance +CREATE EXTENSION IF NOT EXISTS vector; +CREATE EXTENSION IF NOT EXISTS pgcrypto; + +CREATE DATABASE easyai_memory; +\c easyai_memory +CREATE EXTENSION IF NOT EXISTS vector; +CREATE EXTENSION IF NOT EXISTS pgcrypto; +``` + +最终需要准备两个连接串: + +```text +postgresql://USER:PASSWORD@HOST:5432/agent_governance?schema=public +postgresql://USER:PASSWORD@HOST:5432/easyai_memory?schema=public +``` + +## 修改生产配置 + +所有生产配置都在: + +```text +k8s/overlays/production +``` + +### 1. 修改非敏感配置 + +编辑: + +```text +k8s/overlays/production/app-config.yaml +``` + +至少替换以下内容: + +```yaml +NUXT_PUBLIC_BASE_SOCKETURL: wss://你的域名/socket.io +CONFIG_COMFYUI_QUENE_REDIS_HOST: 你的Redis内网地址 +GATEWAY_CLUSTER_REDIS_HOST: 你的Redis内网地址 +ASG_REDIS_HOST: 你的Redis内网地址 +CONFIG_MQ_HOST: 你的RabbitMQ内网地址 +``` + +如果使用统一域名访问,以下配置通常保持默认: + +```yaml +NUXT_PUBLIC_BASE_APIURL: /api +NUXT_PUBLIC_SG_APIURL: /asg-api +``` + +### 2. 修改敏感配置 + +编辑: + +```text +k8s/overlays/production/app-secret.yaml +``` + +至少替换以下内容: + +```yaml +CONFIG_DB_MONGO_URI: mongodb://你的Mongo内网地址:27017 +CONFIG_DB_MONGO_USERNAME: 你的Mongo用户名 +CONFIG_DB_MONGO_PASSWORD: 你的Mongo密码 +MONGO_INITDB_ROOT_USERNAME: 你的Mongo用户名 +MONGO_INITDB_ROOT_PASSWORD: 你的Mongo密码 + +CONFIG_COMFYUI_QUENE_REDIS_PASSWORD: 你的Redis密码 +GATEWAY_CLUSTER_REDIS_PASSWORD: 你的Redis密码 +ASG_REDIS_PASSWORD: 你的Redis密码 + +CONFIG_MQ_USER: 你的RabbitMQ用户名 +CONFIG_MQ_PASSWORD: 你的RabbitMQ密码 + +CONFIG_JWT_SECRET: 一段足够长的随机字符串 +CONFIG_TOKEN_SIGN_SK: 一段足够长的随机字符串 + +ASG_DATABASE_URL: postgresql://USER:PASSWORD@HOST:5432/agent_governance?schema=public +MEMORY_DATABASE_URL: postgresql://USER:PASSWORD@HOST:5432/easyai_memory?schema=public + +ASG_POSTGRES_USER: 你的PostgreSQL用户名 +ASG_POSTGRES_PASSWORD: 你的PostgreSQL密码 +ASG_ADMIN_PASSWORD: EasyAI管理员密码 + +SANDBOX_JUPYTER_TOKEN: 一段足够长的随机字符串 +``` + +生成随机密钥示例: + +```bash +openssl rand -base64 48 +``` + +如果使用 OSS/S3,请同时替换: + +```yaml +OSS_ENDPOINT: "" +OSS_ACCESS_KEY_ID: "" +OSS_ACCESS_KEY_SECRET: "" +OSS_BUCKET: "" +``` + +生产密钥不要提交到公共 Git 仓库。正式环境建议接入 External Secrets、Sealed Secrets 或云厂商 Secret Manager。 + +### 3. 修改域名和证书 + +编辑: + +```text +k8s/overlays/production/ingress.yaml +``` + +将所有 `easyai.example.com` 替换为客户真实域名。 + +如果集群的 IngressClass 不是 `nginx`,同步修改: + +```yaml +ingressClassName: nginx +``` + +创建 TLS Secret: + +```bash +kubectl create namespace easyai --dry-run=client -o yaml | kubectl apply -f - + +kubectl -n easyai create secret tls easyai-tls \ + --cert=/path/to/tls.crt \ + --key=/path/to/tls.key +``` + +如果使用 cert-manager,请按客户集群规范在 `ingress.yaml` 中添加证书签发 annotation。 + +### 4. 修改共享存储 + +编辑: + +```text +k8s/overlays/production/shared-pvc.yaml +``` + +将占位符替换为真实的 RWX StorageClass: + +```yaml +storageClassName: 你的RWX_StorageClass +``` + +默认 PVC 容量为 `100Gi`,可按客户实际文件量调整: + +```yaml +storage: 100Gi +``` + +## 部署前检查 + +确认没有未替换的占位符: + +```bash +grep -R -nE 'CHANGE_ME|easyai.example.com|你的' k8s/overlays/production +``` + +如果还有输出,请先完成配置替换。 + +渲染生产 YAML: + +```bash +kubectl kustomize k8s/overlays/production > /tmp/easyai-production.yaml +``` + +检查资源类型: + +```bash +grep '^kind:' /tmp/easyai-production.yaml +``` + +执行服务端 dry-run: + +```bash +kubectl create namespace easyai --dry-run=client -o yaml | kubectl apply -f - +kubectl apply --dry-run=server -f /tmp/easyai-production.yaml +``` + +如果 dry-run 报 IngressClass、Ingress annotation 或权限相关错误,请先按客户集群规范调整 `ingress.yaml`。 + +## 部署 + +执行部署: + +```bash +kubectl apply -k k8s/overlays/production +``` + +查看资源: + +```bash +kubectl -n easyai get deploy,svc,pvc,ingress,hpa,pdb +kubectl -n easyai get pods -o wide +``` + +等待服务发布完成: + +```bash +for d in easyai-web easyai-server ws-gateway video-edit sandbox easyai-asg agent-memory; do + kubectl -n easyai rollout status deploy/$d --timeout=15m +done +``` + +如果某个 Deployment 超时,查看 Pod 状态和日志: + +```bash +kubectl -n easyai get pods -o wide +kubectl -n easyai describe pod +kubectl -n easyai logs --all-containers --tail=200 +``` + +## DNS 和访问验证 + +查看 Ingress 地址: + +```bash +kubectl -n easyai get ingress +``` + +将客户域名解析到 Ingress 暴露的公网 IP 或 CNAME。 + +DNS 生效前,可以先用 `curl --resolve` 验证: + +```bash +curl -i --resolve 你的域名:443:Ingress公网IP https://你的域名/api/health +``` + +DNS 生效后验证: + +```bash +curl -i https://你的域名/api/health +curl -i https://你的域名/asg-api/health +curl -i https://你的域名/ams-api/health +``` + +浏览器访问: + +```text +https://你的域名 +``` + +重点确认: + +| 检查项 | 预期结果 | +| --- | --- | +| Web 页面 | 可以正常打开 | +| `/api/health` | 返回健康状态,不是 404/502 | +| `/asg-api/health` | 返回健康状态 | +| `/ams-api/health` | 返回健康状态 | +| `/socket.io` | WebSocket 可以正常连接 | + +## 更新到最新版本 + +本仓库默认使用 `latest` 镜像标签,并且工作负载已配置 `imagePullPolicy: Always`。 + +当 EasyAI 发布新版本后,客户在本仓库目录执行以下命令。即使 `git pull` 没有拉到新的 YAML 变更,只要 EasyAI 已发布新的 `latest` 镜像,也需要执行后面的 `rollout restart`: + +```bash +git pull +kubectl apply -k k8s/overlays/production + +kubectl -n easyai rollout restart \ + deploy/easyai-web \ + deploy/easyai-server \ + deploy/ws-gateway \ + deploy/video-edit \ + deploy/sandbox \ + deploy/easyai-asg \ + deploy/agent-memory + +for d in easyai-web easyai-server ws-gateway video-edit sandbox easyai-asg agent-memory; do + kubectl -n easyai rollout status deploy/$d --timeout=15m +done +``` + +说明:`latest` 标签更新后,Kubernetes 不会仅因为远端镜像变化就自动重建 Pod,所以更新时必须执行 `rollout restart`。 + +## 回滚 + +如果是配置变更导致问题,可回到上一份配置后重新部署: + +```bash +git checkout <上一个可用提交> +kubectl apply -k k8s/overlays/production +``` + +如果是镜像版本问题,请使用 EasyAI 提供的回滚版本或回滚仓库版本后重新执行部署。 + +查看发布历史: + +```bash +kubectl -n easyai rollout history deploy/easyai-server +``` + +回滚单个服务: + +```bash +kubectl -n easyai rollout undo deploy/easyai-server +kubectl -n easyai rollout status deploy/easyai-server --timeout=10m +``` + +## 常见问题 + +| 现象 | 优先检查 | +| --- | --- | +| `ImagePullBackOff` | 节点是否能访问镜像仓库,镜像仓库是否需要额外拉取凭证 | +| Pod 一直 `Pending` | 节点资源、PVC 是否 Bound、StorageClass 是否支持 RWX | +| HPA 显示 `unknown` | Metrics Server 是否可用,`kubectl top pods` 是否正常 | +| `/api/health` 返回 404/502 | IngressClass、rewrite annotation、后端服务是否 Ready | +| WebSocket 频繁断开 | `/socket.io` 是否路由到 `ws-gateway:3002`,Ingress 超时时间是否足够 | +| 文件上传后多副本不可见 | `easyai-shared-files` PVC 是否正确挂载到 `easyai-server` | +| 连接数据库或中间件失败 | VPC、安全组、白名单、端口、用户名密码、TLS 要求 | +| `video-edit` 无法创建 | 集群 PodSecurity/PSA 是否禁止 `SYS_ADMIN` capability | + +查看事件和日志: + +```bash +kubectl -n easyai get events --sort-by=.lastTimestamp | tail -80 +kubectl -n easyai logs deploy/easyai-server --tail=200 +kubectl -n easyai logs deploy/ws-gateway --tail=200 +kubectl -n easyai logs deploy/easyai-asg --tail=200 +kubectl -n easyai logs deploy/agent-memory --tail=200 +``` diff --git a/k8s/base/agent-memory.yaml b/k8s/base/agent-memory.yaml new file mode 100644 index 0000000..38d9435 --- /dev/null +++ b/k8s/base/agent-memory.yaml @@ -0,0 +1,82 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: agent-memory + labels: + app.kubernetes.io/name: agent-memory +spec: + replicas: 2 + revisionHistoryLimit: 3 + selector: + matchLabels: + app.kubernetes.io/name: agent-memory + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + template: + metadata: + labels: + app.kubernetes.io/name: agent-memory + spec: + serviceAccountName: easyai-workload + terminationGracePeriodSeconds: 45 + containers: + - name: agent-memory + image: registry.cn-shanghai.aliyuncs.com/easyaigc/agent-memory:latest + imagePullPolicy: Always + ports: + - name: http + containerPort: 3004 + - name: tcp + containerPort: 4004 + envFrom: + - configMapRef: + name: easyai-app-config + - secretRef: + name: easyai-app-secret + env: + - name: MEMORY_AI_BASE_URL + value: http://easyai-server:3001 + readinessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 20 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + livenessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 60 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 3 + resources: + requests: + cpu: 200m + memory: 256Mi + limits: + cpu: "1" + memory: 512Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: agent-memory + labels: + app.kubernetes.io/name: agent-memory +spec: + type: ClusterIP + selector: + app.kubernetes.io/name: agent-memory + ports: + - name: http + port: 3004 + targetPort: http + - name: tcp + port: 4004 + targetPort: tcp diff --git a/k8s/base/easyai-asg.yaml b/k8s/base/easyai-asg.yaml new file mode 100644 index 0000000..01f0e7a --- /dev/null +++ b/k8s/base/easyai-asg.yaml @@ -0,0 +1,92 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: easyai-asg + labels: + app.kubernetes.io/name: easyai-asg +spec: + replicas: 2 + revisionHistoryLimit: 3 + selector: + matchLabels: + app.kubernetes.io/name: easyai-asg + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + template: + metadata: + labels: + app.kubernetes.io/name: easyai-asg + spec: + serviceAccountName: easyai-workload + terminationGracePeriodSeconds: 45 + containers: + - name: easyai-asg + image: registry.cn-shanghai.aliyuncs.com/easyaigc/agent-governance:latest + imagePullPolicy: Always + ports: + - name: http + containerPort: 3003 + - name: tcp + containerPort: 4003 + envFrom: + - configMapRef: + name: easyai-app-config + - secretRef: + name: easyai-app-secret + env: + - name: ASG_TCP_HOST + value: 0.0.0.0 + - name: ASG_TCP_PORT + value: "4003" + - name: ASG_MAIN_BACKEND_URL + value: http://easyai-server:3001 + volumeMounts: + - name: pm2 + mountPath: /app/.pm2 + readinessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 20 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + livenessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 60 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 3 + resources: + requests: + cpu: 200m + memory: 256Mi + limits: + cpu: "1" + memory: 512Mi + volumes: + - name: pm2 + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: easyai-asg + labels: + app.kubernetes.io/name: easyai-asg +spec: + type: ClusterIP + selector: + app.kubernetes.io/name: easyai-asg + ports: + - name: http + port: 3003 + targetPort: http + - name: tcp + port: 4003 + targetPort: tcp diff --git a/k8s/base/easyai-server.yaml b/k8s/base/easyai-server.yaml new file mode 100644 index 0000000..d8f8340 --- /dev/null +++ b/k8s/base/easyai-server.yaml @@ -0,0 +1,133 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: easyai-server + labels: + app.kubernetes.io/name: easyai-server +spec: + replicas: 2 + revisionHistoryLimit: 3 + selector: + matchLabels: + app.kubernetes.io/name: easyai-server + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + template: + metadata: + labels: + app.kubernetes.io/name: easyai-server + spec: + serviceAccountName: easyai-workload + terminationGracePeriodSeconds: 60 + containers: + - name: easyai-server + image: registry.cn-shanghai.aliyuncs.com/comfy-ai/comfy-server:latest + imagePullPolicy: Always + ports: + - name: http + containerPort: 3001 + - name: tcp-inbound + containerPort: 4001 + envFrom: + - configMapRef: + name: easyai-app-config + - secretRef: + name: easyai-app-secret + env: + - name: CONFIG_INSTANCE_ID + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: CONFIG_VIDEO_EDIT_API_URL + value: http://video-edit:8000 + - name: SANDBOX_SERVICE_BASE_URL + value: http://sandbox:8000 + - name: ASG_TCP_HOST + value: easyai-asg + - name: ASG_TCP_PORT + value: "4003" + - name: MEMORY_TCP_HOST + value: agent-memory + - name: MEMORY_TCP_PORT + value: "4004" + - name: GATEWAY_INBOUND_ENABLED + value: "true" + - name: GATEWAY_INBOUND_TCP_LISTEN_HOST + value: 0.0.0.0 + - name: GATEWAY_INBOUND_TCP_PORT + value: "4001" + - name: WS_GATEWAY_TCP_HOST + value: ws-gateway + - name: WS_GATEWAY_TCP_PORT + value: "4002" + volumeMounts: + - name: shared-files + mountPath: /app/upload + subPath: upload + - name: shared-files + mountPath: /app/backups + subPath: backups + - name: shared-files + mountPath: /app/restores + subPath: restores + - name: app-tmp + mountPath: /app/tmp + - name: tmp + mountPath: /tmp + startupProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 20 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 30 + readinessProbe: + httpGet: + path: /health + port: http + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + livenessProbe: + httpGet: + path: /health + port: http + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 3 + resources: + requests: + cpu: 500m + memory: 1Gi + limits: + cpu: "2" + memory: 2500Mi + volumes: + - name: shared-files + emptyDir: {} + - name: app-tmp + emptyDir: {} + - name: tmp + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: easyai-server + labels: + app.kubernetes.io/name: easyai-server +spec: + type: ClusterIP + selector: + app.kubernetes.io/name: easyai-server + ports: + - name: http + port: 3001 + targetPort: http + - name: tcp-inbound + port: 4001 + targetPort: tcp-inbound diff --git a/k8s/base/easyai-web.yaml b/k8s/base/easyai-web.yaml new file mode 100644 index 0000000..29958be --- /dev/null +++ b/k8s/base/easyai-web.yaml @@ -0,0 +1,77 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: easyai-web + labels: + app.kubernetes.io/name: easyai-web +spec: + replicas: 2 + revisionHistoryLimit: 3 + selector: + matchLabels: + app.kubernetes.io/name: easyai-web + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + template: + metadata: + labels: + app.kubernetes.io/name: easyai-web + spec: + serviceAccountName: easyai-workload + terminationGracePeriodSeconds: 30 + containers: + - name: easyai-web + image: registry.cn-shanghai.aliyuncs.com/comfy-ai/one-ai:latest + imagePullPolicy: Always + ports: + - name: http + containerPort: 3010 + envFrom: + - configMapRef: + name: easyai-app-config + - secretRef: + name: easyai-app-secret + env: + - name: NUXT_PUBLIC_BASE_SSRURL + value: http://easyai-server:3001 + readinessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 15 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + livenessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 60 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 3 + resources: + requests: + cpu: 200m + memory: 512Mi + limits: + cpu: "1" + memory: 1500Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: easyai-web + labels: + app.kubernetes.io/name: easyai-web +spec: + type: ClusterIP + selector: + app.kubernetes.io/name: easyai-web + ports: + - name: http + port: 3010 + targetPort: http diff --git a/k8s/base/kustomization.yaml b/k8s/base/kustomization.yaml new file mode 100644 index 0000000..409ea09 --- /dev/null +++ b/k8s/base/kustomization.yaml @@ -0,0 +1,15 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - service-account.yaml + - easyai-web.yaml + - easyai-server.yaml + - ws-gateway.yaml + - video-edit.yaml + - sandbox.yaml + - easyai-asg.yaml + - agent-memory.yaml + +commonLabels: + app.kubernetes.io/part-of: easyai diff --git a/k8s/base/sandbox.yaml b/k8s/base/sandbox.yaml new file mode 100644 index 0000000..aa03cc8 --- /dev/null +++ b/k8s/base/sandbox.yaml @@ -0,0 +1,139 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: sandbox + labels: + app.kubernetes.io/name: sandbox +spec: + replicas: 1 + revisionHistoryLimit: 3 + selector: + matchLabels: + app.kubernetes.io/name: sandbox + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + template: + metadata: + labels: + app.kubernetes.io/name: sandbox + spec: + serviceAccountName: easyai-workload + terminationGracePeriodSeconds: 30 + containers: + - name: sandbox + image: registry.cn-shanghai.aliyuncs.com/easyaigc/sandbox:latest + imagePullPolicy: Always + ports: + - name: http + containerPort: 8000 + - name: jupyter + containerPort: 8888 + envFrom: + - configMapRef: + name: easyai-app-config + - secretRef: + name: easyai-app-secret + env: + - name: NODE_PATH + value: /app/node_modules:/app/node_modules_extra/node_modules + - name: NODE_MODULES_EXTRA + value: /app/node_modules_extra + - name: NODE_MODULES_EXTRA_PATH + value: /app/node_modules_extra/node_modules + - name: SANDBOX_TMPDIR + value: /app/tmp + - name: PIP_CACHE_DIR + value: /app/tmp/.pip + - name: TMPDIR + value: /app/tmp + - name: NPM_CONFIG_CACHE + value: /app/tmp/.npm + - name: PYTHON_EXTRA_DIR + value: /root/.local/lib/python3.12/site-packages + - name: PYTHON_EXTRA_PATH + value: /root/.local/lib/python3.12/site-packages + - name: JUPYTER_DATA_DIR + value: /app/tmp/jupyter_data + - name: JUPYTER_RUNTIME_DIR + value: /app/tmp/jupyter_runtime + - name: JUPYTER_CONFIG_DIR + value: /app/tmp/jupyter_config + - name: JUPYTER_TOKEN + valueFrom: + secretKeyRef: + name: easyai-app-secret + key: SANDBOX_JUPYTER_TOKEN + volumeMounts: + - name: data + mountPath: /app/data + - name: local-packages + mountPath: /app/local_packages + - name: python-packages + mountPath: /root/.local + - name: node-modules-extra + mountPath: /app/node_modules_extra + - name: app-tmp + mountPath: /app/tmp + - name: tmp + mountPath: /tmp + readinessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + livenessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 45 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 3 + resources: + requests: + cpu: 500m + memory: 512Mi + limits: + cpu: "1" + memory: 1Gi + securityContext: + allowPrivilegeEscalation: false + seccompProfile: + type: RuntimeDefault + volumes: + - name: data + emptyDir: {} + - name: local-packages + emptyDir: {} + - name: python-packages + emptyDir: {} + - name: node-modules-extra + emptyDir: {} + - name: app-tmp + emptyDir: {} + - name: tmp + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: sandbox + labels: + app.kubernetes.io/name: sandbox +spec: + type: ClusterIP + selector: + app.kubernetes.io/name: sandbox + ports: + - name: http + port: 8000 + targetPort: http + - name: jupyter + port: 8888 + targetPort: jupyter diff --git a/k8s/base/service-account.yaml b/k8s/base/service-account.yaml new file mode 100644 index 0000000..dc27881 --- /dev/null +++ b/k8s/base/service-account.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: easyai-workload + labels: + app.kubernetes.io/name: easyai-workload diff --git a/k8s/base/video-edit.yaml b/k8s/base/video-edit.yaml new file mode 100644 index 0000000..44c9e12 --- /dev/null +++ b/k8s/base/video-edit.yaml @@ -0,0 +1,97 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: video-edit + labels: + app.kubernetes.io/name: video-edit +spec: + replicas: 1 + revisionHistoryLimit: 3 + selector: + matchLabels: + app.kubernetes.io/name: video-edit + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + template: + metadata: + labels: + app.kubernetes.io/name: video-edit + spec: + serviceAccountName: easyai-workload + terminationGracePeriodSeconds: 60 + containers: + - name: video-edit + image: registry.cn-shanghai.aliyuncs.com/easyaigc/videoedit:latest + imagePullPolicy: Always + ports: + - name: http + containerPort: 8000 + envFrom: + - configMapRef: + name: easyai-app-config + - secretRef: + name: easyai-app-secret + env: + - name: PYTHONUNBUFFERED + value: "1" + - name: OMP_NUM_THREADS + value: "4" + - name: OPENBLAS_NUM_THREADS + value: "4" + volumeMounts: + - name: temp + mountPath: /app/temp + - name: dshm + mountPath: /dev/shm + readinessProbe: + httpGet: + path: /docs + port: http + initialDelaySeconds: 20 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + livenessProbe: + httpGet: + path: /docs + port: http + initialDelaySeconds: 60 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 3 + resources: + requests: + cpu: "1" + memory: 2Gi + limits: + cpu: "2" + memory: 4Gi + securityContext: + capabilities: + add: + - SYS_ADMIN + volumes: + - name: temp + emptyDir: {} + - name: dshm + emptyDir: + medium: Memory + sizeLimit: 3Gi +--- +apiVersion: v1 +kind: Service +metadata: + name: video-edit + labels: + app.kubernetes.io/name: video-edit +spec: + type: ClusterIP + selector: + app.kubernetes.io/name: video-edit + ports: + - name: http + port: 8000 + targetPort: http diff --git a/k8s/base/ws-gateway.yaml b/k8s/base/ws-gateway.yaml new file mode 100644 index 0000000..eef30a4 --- /dev/null +++ b/k8s/base/ws-gateway.yaml @@ -0,0 +1,107 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ws-gateway + labels: + app.kubernetes.io/name: ws-gateway +spec: + replicas: 2 + revisionHistoryLimit: 3 + selector: + matchLabels: + app.kubernetes.io/name: ws-gateway + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + template: + metadata: + labels: + app.kubernetes.io/name: ws-gateway + spec: + serviceAccountName: easyai-workload + terminationGracePeriodSeconds: 45 + containers: + - name: ws-gateway + image: registry.cn-shanghai.aliyuncs.com/easyaigc/wsgateway:latest + imagePullPolicy: Always + ports: + - name: websocket + containerPort: 3002 + - name: tcp-events + containerPort: 4002 + envFrom: + - configMapRef: + name: easyai-app-config + - secretRef: + name: easyai-app-secret + env: + - name: GATEWAY_CLUSTER_NODE_ID + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: CONFIG_TCP_HOST + value: 0.0.0.0 + - name: CONFIG_TCP_PORT + value: "4002" + - name: GATEWAY_INBOUND_ENABLED + value: "true" + - name: GATEWAY_INBOUND_TCP_HOST + value: easyai-server + - name: GATEWAY_INBOUND_TCP_PORT + value: "4001" + volumeMounts: + - name: pm2 + mountPath: /app/.pm2 + - name: tmp + mountPath: /tmp + readinessProbe: + tcpSocket: + port: websocket + initialDelaySeconds: 15 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + livenessProbe: + tcpSocket: + port: websocket + initialDelaySeconds: 60 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 3 + resources: + requests: + cpu: 200m + memory: 256Mi + limits: + cpu: "1" + memory: 512Mi + securityContext: + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + seccompProfile: + type: RuntimeDefault + volumes: + - name: pm2 + emptyDir: {} + - name: tmp + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: ws-gateway + labels: + app.kubernetes.io/name: ws-gateway +spec: + type: ClusterIP + selector: + app.kubernetes.io/name: ws-gateway + ports: + - name: websocket + port: 3002 + targetPort: websocket + - name: tcp-events + port: 4002 + targetPort: tcp-events diff --git a/k8s/overlays/local/README.md b/k8s/overlays/local/README.md new file mode 100644 index 0000000..54003c9 --- /dev/null +++ b/k8s/overlays/local/README.md @@ -0,0 +1,426 @@ +# EasyAI 本地 kind 部署指南 + +本目录用于在本机通过 kind 部署 EasyAI,验证 Kubernetes YAML 是否可以正常渲染、创建资源、拉起 Pod,并完成前端和核心接口访问。 + +本地部署会在 kind 集群中同时启动以下依赖: + +| 依赖 | 说明 | +| --- | --- | +| MongoDB | 主服务本地业务数据 | +| Redis | 队列、缓存、WebSocket 集群状态 | +| RabbitMQ | 本地消息队列 | +| PostgreSQL/pgvector | Agent 治理和记忆服务数据库 | + +本地数据使用 `emptyDir`,删除 Pod 或集群后数据可能丢失。本地部署只用于验证,不代表生产高可用方案。 + +## 1. 本机准备 + +以下命令均在仓库根目录执行。如果当前已经在仓库根目录,可以跳过这一步;如果当前在本 README 所在目录,执行: + +```bash +cd ../../.. +``` + +确认 Docker 正在运行: + +```bash +docker version +``` + +确认已有 `kubectl`: + +```bash +kubectl version --client +``` + +安装 kind: + +```bash +brew install kind +``` + +确认 kind 可用: + +```bash +kind version +``` + +建议本机至少预留: + +| 资源 | 建议 | +| --- | --- | +| CPU | 4 核以上 | +| 内存 | 12 GB 以上 | +| 磁盘 | 20 GB 以上 | + +如果本机是 Apple Silicon,且某些镜像没有 arm64 版本,可能出现 `ImagePullBackOff`、`exec format error` 或 Pod 无法启动。遇到这种情况,先查看失败镜像和 Pod 事件,生产环境请使用 amd64 节点池或多架构镜像。 + +## 2. 创建 kind 集群 + +如果本机还没有 `easyai` 集群,执行: + +```bash +kind create cluster --name easyai +``` + +如果之前已经创建过同名集群,并且希望从干净环境重新部署,执行: + +```bash +kind delete cluster --name easyai +kind create cluster --name easyai +``` + +切换到 kind 集群: + +```bash +kubectl config use-context kind-easyai +``` + +确认当前 context 和节点: + +```bash +kubectl config current-context +kubectl get nodes -o wide +``` + +当前 context 应该是: + +```text +kind-easyai +``` + +## 3. 部署前检查 + +渲染本地 YAML: + +```bash +kubectl kustomize k8s/overlays/local > /tmp/easyai-local.yaml +``` + +检查资源类型: + +```bash +grep '^kind:' /tmp/easyai-local.yaml +``` + +准备 dry-run 需要的 namespace: + +```bash +kubectl create namespace easyai-local --dry-run=client -o yaml | kubectl apply -f - +``` + +执行服务端 dry-run: + +```bash +kubectl apply --dry-run=server -f /tmp/easyai-local.yaml +``` + +如果只想检查 YAML 是否能被本地解析,也可以执行: + +```bash +kubectl apply --dry-run=client -f /tmp/easyai-local.yaml +``` + +## 4. 部署 EasyAI + +执行部署: + +```bash +kubectl apply -k k8s/overlays/local +``` + +查看 namespace: + +```bash +kubectl get ns easyai-local +``` + +查看资源: + +```bash +kubectl -n easyai-local get deploy,svc,pods +``` + +等待依赖和应用启动: + +```bash +for d in mongo redis rabbitmq easyai-pgvector easyai-server easyai-web ws-gateway video-edit sandbox easyai-asg agent-memory; do + kubectl -n easyai-local rollout status deploy/$d --timeout=15m +done +``` + +确认 Pod 状态: + +```bash +kubectl -n easyai-local get pods -o wide +``` + +所有核心 Pod 都应进入 `Running`,并且 `READY` 数量正常。 + +## 5. 本地端口转发 + +本地前端配置使用 `127.0.0.1`,因此需要在本机打开端口转发。 + +建议打开 5 个终端窗口,并分别保持以下命令运行。 + +终端 1: + +```bash +kubectl -n easyai-local port-forward svc/easyai-web 3010:3010 +``` + +终端 2: + +```bash +kubectl -n easyai-local port-forward svc/easyai-server 3001:3001 +``` + +终端 3: + +```bash +kubectl -n easyai-local port-forward svc/ws-gateway 3002:3002 +``` + +终端 4: + +```bash +kubectl -n easyai-local port-forward svc/easyai-asg 3003:3003 +``` + +终端 5: + +```bash +kubectl -n easyai-local port-forward svc/agent-memory 3004:3004 +``` + +端口被占用时,先查看占用进程: + +```bash +lsof -iTCP:3010 -sTCP:LISTEN +lsof -iTCP:3001 -sTCP:LISTEN +lsof -iTCP:3002 -sTCP:LISTEN +lsof -iTCP:3003 -sTCP:LISTEN +lsof -iTCP:3004 -sTCP:LISTEN +``` + +停止占用进程后,重新执行对应的 `port-forward`。 + +## 6. 访问验证 + +确认主服务: + +```bash +curl -i http://127.0.0.1:3001/health +``` + +确认 ASG: + +```bash +curl -i http://127.0.0.1:3003/health +``` + +确认 Agent Memory: + +```bash +curl -i http://127.0.0.1:3004/health +``` + +打开前端: + +```bash +open http://127.0.0.1:3010 +``` + +本地默认管理员账号: + +```text +admin / 123456 +``` + +如果页面可以打开,但接口失败,请先确认 `3001`、`3002`、`3003`、`3004` 的 `port-forward` 仍在运行。 + +## 7. 查看状态和日志 + +查看 Service: + +```bash +kubectl -n easyai-local get svc +``` + +查看 Endpoints: + +```bash +kubectl -n easyai-local get endpoints easyai-web easyai-server ws-gateway easyai-asg agent-memory mongo redis rabbitmq easyai-pgvector +``` + +查看应用日志: + +```bash +kubectl -n easyai-local logs deploy/easyai-server --tail=200 +kubectl -n easyai-local logs deploy/ws-gateway --tail=200 +kubectl -n easyai-local logs deploy/easyai-asg --tail=200 +kubectl -n easyai-local logs deploy/agent-memory --tail=200 +``` + +查看集群事件: + +```bash +kubectl -n easyai-local get events --sort-by=.lastTimestamp | tail -80 +``` + +## 8. 更新本地部署 + +如果代码仓库或镜像更新后需要重新部署,执行: + +```bash +git pull +kubectl apply -k k8s/overlays/local + +kubectl -n easyai-local rollout restart \ + deploy/easyai-web \ + deploy/easyai-server \ + deploy/ws-gateway \ + deploy/video-edit \ + deploy/sandbox \ + deploy/easyai-asg \ + deploy/agent-memory + +for d in easyai-server easyai-web ws-gateway video-edit sandbox easyai-asg agent-memory; do + kubectl -n easyai-local rollout status deploy/$d --timeout=15m +done +``` + +说明:应用镜像使用 `latest`,远端镜像更新后需要 `rollout restart` 才会重新创建 Pod 并拉取镜像。 + +## 9. 常见问题 + +### kubectl 连接不到集群 + +检查 context: + +```bash +kubectl config get-contexts +kubectl config current-context +``` + +切换回本地 kind 集群: + +```bash +kubectl config use-context kind-easyai +``` + +### Pod 一直 Pending + +查看原因: + +```bash +kubectl -n easyai-local describe pod +``` + +常见原因: + +- 本机 CPU 或内存不足。 +- Docker 没有足够可用资源。 +- 镜像架构和本机架构不匹配。 + +### ImagePullBackOff + +查看具体镜像: + +```bash +kubectl -n easyai-local get pod -o jsonpath='{.spec.containers[*].image}' +``` + +查看事件: + +```bash +kubectl -n easyai-local describe pod +``` + +常见原因: + +- 本机网络无法访问 `registry.cn-shanghai.aliyuncs.com`。 +- 镜像 tag 不存在。 +- 镜像没有当前 CPU 架构版本。 + +### CrashLoopBackOff + +查看日志: + +```bash +kubectl -n easyai-local logs --all-containers --previous --tail=200 +kubectl -n easyai-local logs --all-containers --tail=200 +``` + +常见原因: + +- 依赖服务还没 Ready。 +- MongoDB、Redis、RabbitMQ 或 PostgreSQL 初始化较慢。 +- 镜像启动时执行数据库迁移失败。 + +可以先等依赖 Ready,再重启应用 Pod: + +```bash +kubectl -n easyai-local rollout restart deploy/easyai-server deploy/easyai-asg deploy/agent-memory +``` + +### easyai-server Mongo 认证失败 + +如果 `easyai-server` 日志出现: + +```text +MongoServerError: Authentication failed +UserNotFound: Could not find user "username" for db "admin" +``` + +通常是 MongoDB 首次初始化账号过程中被重启,导致 `/data/db` 已经非空但 root 用户没有创建完成。本地 overlay 使用 `emptyDir`,可以直接重建 Mongo Pod,让它按当前 Secret 重新初始化: + +```bash +kubectl -n easyai-local delete pod -l app.kubernetes.io/name=mongo +kubectl -n easyai-local wait --for=condition=Ready pod -l app.kubernetes.io/name=mongo --timeout=5m +kubectl -n easyai-local rollout restart deploy/easyai-server deploy/easyai-web +``` + +### 端口转发断开 + +`kubectl port-forward` 会受到本地网络、终端关闭和 Pod 重启影响,断开后重新执行即可。 + +如果重新执行仍失败,先确认 Service 有 endpoints: + +```bash +kubectl -n easyai-local get endpoints easyai-web easyai-server ws-gateway easyai-asg agent-memory +``` + +### 前端打开但接口不通 + +确认所有 `port-forward` 正在运行: + +```bash +lsof -iTCP:3010 -sTCP:LISTEN +lsof -iTCP:3001 -sTCP:LISTEN +lsof -iTCP:3002 -sTCP:LISTEN +lsof -iTCP:3003 -sTCP:LISTEN +lsof -iTCP:3004 -sTCP:LISTEN +``` + +重新验证接口: + +```bash +curl -i http://127.0.0.1:3001/health +curl -i http://127.0.0.1:3003/health +curl -i http://127.0.0.1:3004/health +``` + +## 10. 清理本地环境 + +删除 EasyAI 本地部署: + +```bash +kubectl delete -k k8s/overlays/local +``` + +如果不再需要本地 kind 集群: + +```bash +kind delete cluster --name easyai +``` diff --git a/k8s/overlays/local/app-config.yaml b/k8s/overlays/local/app-config.yaml new file mode 100644 index 0000000..d132b40 --- /dev/null +++ b/k8s/overlays/local/app-config.yaml @@ -0,0 +1,91 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: easyai-app-config +data: + NUXT_PUBLIC_BASE_APIURL: http://127.0.0.1:3001 + NUXT_PUBLIC_BASE_SOCKETURL: ws://127.0.0.1:3002 + NUXT_PUBLIC_SG_APIURL: http://127.0.0.1:3003 + NUXT_APP_CDN_URL: "" + + MALLOC_ARENA_MAX: "2" + MALLOC_TRIM_THRESHOLD_: "131072" + MALLOC_MMAP_THRESHOLD_: "131072" + MALLOC_TOP_PAD_: "131072" + SHARP_CACHE_ENABLED: "false" + SHARP_CONCURRENCY: "1" + + CONFIG_COMFYUI_QUENE_REDIS_HOST: redis + CONFIG_COMFYUI_QUENE_REDIS_PORT: "6379" + CONFIG_COMFYUI_QUENE_REDIS_USERNAME: "" + CONFIG_COMFYUI_QUENE_REDIS_DB: "6" + CONFIG_COMFYUI_CACHE_REDIS_DB: "11" + + LOG_LEVEL: log,error,warn,debug + WS_GATEWAY_LOG_LEVEL: log,error,warn + CONFIG_TOKEN_EXPIRE: "1800" + CONFIG_PROXY_URL: "" + CONFIG_DOCS_DISABLE: "false" + + CONFIG_MQ_PROTOCOL: amqp + CONFIG_MQ_HOST: rabbitmq + CONFIG_MQ_PORT: "5672" + CONFIG_MQ_VHOST: / + + CONFIG_TCP_HOST: 0.0.0.0 + CONFIG_TCP_PORT: "4002" + GATEWAY_CLUSTER_REDIS_ENABLED: "true" + GATEWAY_CLUSTER_REDIS_CHANNEL: easyai:wsgateway:cluster + GATEWAY_CLUSTER_REDIS_ROUTE_PREFIX: easyai:wsgateway:route + GATEWAY_CLUSTER_REDIS_ROUTE_TTL_SEC: "120" + GATEWAY_CLUSTER_REDIS_RECONNECT_INTERVAL_MS: "5000" + GATEWAY_CLUSTER_REDIS_URL: "" + GATEWAY_CLUSTER_REDIS_HOST: redis + GATEWAY_CLUSTER_REDIS_PORT: "6379" + GATEWAY_CLUSTER_REDIS_USERNAME: "" + GATEWAY_CLUSTER_REDIS_DB: "0" + WS_GATEWAY_TCP_EVENT_PATTERN: gateway.event.publish + WS_GATEWAY_TCP_TIMEOUT_MS: "1500" + GATEWAY_INBOUND_TCP_PATTERN: gateway.inbound.command + GATEWAY_INBOUND_TIMEOUT_MS: "5000" + + WS_AUTH_REQUIRED: "false" + WS_AUTH_TIMEOUT_MS: "6000" + WS_AUTH_METHODS: none,bearer + WS_AUTH_WS_TICKETS: "" + + ASG_REDIS_HOST: redis + ASG_REDIS_PORT: "6379" + ASG_REDIS_DB: "8" + ASG_PORT: "3003" + ASG_TCP_HOST: 0.0.0.0 + ASG_TCP_PORT: "4003" + ASG_DOCS_DISABLE: "false" + ASG_MAIN_BACKEND_URL: http://easyai-server:3001 + + AMS_PORT: "3004" + MEMORY_AI_BASE_URL: http://easyai-server:3001 + MEMORY_EMBEDDINGS_PATH: /v1/embeddings + MEMORY_EMBEDDING_MODELS_PATH: /v1/embeddings/models + MEMORY_EMBEDDING_DIMENSION: "1024" + MEMORY_EMBEDDING_MODEL: Qwen3-Embedding-v4 + MEMORY_SCORE_CONFIDENCE_BASE: "0.6" + MEMORY_SCORE_CONFIDENCE_GAIN: "0.4" + MEMORY_SCORE_IMPORTANCE_BASE: "0.7" + MEMORY_SCORE_IMPORTANCE_GAIN: "0.3" + MEMORY_DEDUP_MIN_SCORE: "0.92" + MEMORY_TEXT_DEDUP_MIN_SCORE: "0.9" + MEMORY_CHAT_COMPLETIONS_PATH: /v1/chat/completions + MEMORY_CHAT_MODELS_PATH: /v1/models + MEMORY_CHAT_MODEL: qwen-plus + MEMORY_COMPRESSION_ENABLED: "true" + MEMORY_COMPRESSION_MIN_LENGTH: "80" + MEMORY_MAINTENANCE_ENABLED: "true" + MEMORY_MAINTENANCE_DRY_RUN: "false" + MEMORY_MAINTENANCE_BATCH_SIZE: "200" + MEMORY_MAINTENANCE_LLM_ENABLED: "true" + MEMORY_MAINTENANCE_LLM_MAX_PAIRS: "20" + MEMORY_DOCS_DISABLE: "false" + + OSS_REGION: us-east-1 + OSS_DOMAIN: "" diff --git a/k8s/overlays/local/app-secret.yaml b/k8s/overlays/local/app-secret.yaml new file mode 100644 index 0000000..307c0ac --- /dev/null +++ b/k8s/overlays/local/app-secret.yaml @@ -0,0 +1,38 @@ +apiVersion: v1 +kind: Secret +metadata: + name: easyai-app-secret +type: Opaque +stringData: + CONFIG_DB_MONGO_URI: mongodb://mongo:27017 + CONFIG_DB_MONGO_USERNAME: username + CONFIG_DB_MONGO_PASSWORD: password + MONGO_INITDB_ROOT_USERNAME: username + MONGO_INITDB_ROOT_PASSWORD: password + + CONFIG_COMFYUI_QUENE_REDIS_PASSWORD: "" + GATEWAY_CLUSTER_REDIS_PASSWORD: "" + ASG_REDIS_PASSWORD: "" + + CONFIG_MQ_USER: admin + CONFIG_MQ_PASSWORD: easyai2025 + + CONFIG_JWT_SECRET: this is a very secret secret + CONFIG_TOKEN_SIGN_SK: easyai2025easyai + WS_AUTH_BEARER_TOKENS: "" + + ASG_DATABASE_URL: postgresql://easyai:easyai2025@easyai-pgvector:5432/agent_governance?schema=public + MEMORY_DATABASE_URL: postgresql://easyai:easyai2025@easyai-pgvector:5432/easyai_memory?schema=public + ASG_POSTGRES_USER: easyai + ASG_POSTGRES_PASSWORD: easyai2025 + ASG_POSTGRES_DB: agent_governance + ASG_ADMIN_USERNAME: admin + ASG_ADMIN_PASSWORD: "123456" + MEMORY_AI_API_KEY: "" + + SANDBOX_JUPYTER_TOKEN: easyaiisbest + + OSS_ENDPOINT: "" + OSS_ACCESS_KEY_ID: "" + OSS_ACCESS_KEY_SECRET: "" + OSS_BUCKET: "" diff --git a/k8s/overlays/local/dependencies.yaml b/k8s/overlays/local/dependencies.yaml new file mode 100644 index 0000000..904c806 --- /dev/null +++ b/k8s/overlays/local/dependencies.yaml @@ -0,0 +1,365 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: redis-config +data: + redis.conf: | + port 6379 + replicaof no one + daemonize no + appendonly yes + dir /data + timeout 300 + maxclients 10000 + tcp-keepalive 60 + save "" + tcp-backlog 511 + replica-read-only no + maxmemory 2gb + maxmemory-policy noeviction + databases 16 + appendfsync everysec + no-appendfsync-on-rewrite yes + auto-aof-rewrite-percentage 100 + auto-aof-rewrite-min-size 64mb + slowlog-log-slower-than 10000 + slowlog-max-len 128 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: pgvector-init +data: + 02-init-pgvector.sql: | + \c agent_governance + CREATE EXTENSION IF NOT EXISTS vector; + CREATE EXTENSION IF NOT EXISTS pgcrypto; + + CREATE DATABASE easyai_memory; + GRANT ALL PRIVILEGES ON DATABASE easyai_memory TO easyai; + + \c easyai_memory + CREATE EXTENSION IF NOT EXISTS vector; + CREATE EXTENSION IF NOT EXISTS pgcrypto; +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mongo + labels: + app.kubernetes.io/name: mongo +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: mongo + template: + metadata: + labels: + app.kubernetes.io/name: mongo + spec: + containers: + - name: mongo + image: registry.cn-shanghai.aliyuncs.com/comfy-ai/mongo-aliyun:latest + imagePullPolicy: IfNotPresent + args: + - mongod + - --wiredTigerCacheSizeGB + - "1.5" + ports: + - name: mongo + containerPort: 27017 + env: + - name: MONGO_INITDB_ROOT_USERNAME + valueFrom: + secretKeyRef: + name: easyai-app-secret + key: MONGO_INITDB_ROOT_USERNAME + - name: MONGO_INITDB_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: easyai-app-secret + key: MONGO_INITDB_ROOT_PASSWORD + volumeMounts: + - name: data + mountPath: /data/db + readinessProbe: + tcpSocket: + port: mongo + initialDelaySeconds: 20 + periodSeconds: 10 + startupProbe: + tcpSocket: + port: mongo + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 60 + livenessProbe: + tcpSocket: + port: mongo + initialDelaySeconds: 60 + periodSeconds: 30 + resources: + requests: + cpu: 250m + memory: 1Gi + limits: + cpu: "1" + memory: 2500Mi + volumes: + - name: data + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: mongo + labels: + app.kubernetes.io/name: mongo +spec: + selector: + app.kubernetes.io/name: mongo + ports: + - name: mongo + port: 27017 + targetPort: mongo +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis + labels: + app.kubernetes.io/name: redis +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: redis + template: + metadata: + labels: + app.kubernetes.io/name: redis + spec: + containers: + - name: redis + image: registry.cn-shanghai.aliyuncs.com/comfy-ai/redis-aliyun:latest + imagePullPolicy: IfNotPresent + command: + - redis-server + - /etc/redis/redis.conf + ports: + - name: redis + containerPort: 6379 + volumeMounts: + - name: config + mountPath: /etc/redis/redis.conf + subPath: redis.conf + readOnly: true + - name: data + mountPath: /data + readinessProbe: + tcpSocket: + port: redis + initialDelaySeconds: 5 + periodSeconds: 10 + livenessProbe: + tcpSocket: + port: redis + initialDelaySeconds: 30 + periodSeconds: 30 + resources: + requests: + cpu: 200m + memory: 512Mi + limits: + cpu: "1" + memory: 2Gi + volumes: + - name: config + configMap: + name: redis-config + - name: data + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: redis + labels: + app.kubernetes.io/name: redis +spec: + selector: + app.kubernetes.io/name: redis + ports: + - name: redis + port: 6379 + targetPort: redis +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: rabbitmq + labels: + app.kubernetes.io/name: rabbitmq +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: rabbitmq + template: + metadata: + labels: + app.kubernetes.io/name: rabbitmq + spec: + containers: + - name: rabbitmq + image: registry.cn-shanghai.aliyuncs.com/easyaigc/mq:latest + imagePullPolicy: IfNotPresent + ports: + - name: amqp + containerPort: 5672 + - name: management + containerPort: 15672 + env: + - name: RABBITMQ_DEFAULT_USER + valueFrom: + secretKeyRef: + name: easyai-app-secret + key: CONFIG_MQ_USER + - name: RABBITMQ_DEFAULT_PASS + valueFrom: + secretKeyRef: + name: easyai-app-secret + key: CONFIG_MQ_PASSWORD + volumeMounts: + - name: data + mountPath: /var/lib/rabbitmq + readinessProbe: + tcpSocket: + port: amqp + initialDelaySeconds: 20 + periodSeconds: 10 + livenessProbe: + tcpSocket: + port: amqp + initialDelaySeconds: 60 + periodSeconds: 30 + resources: + requests: + cpu: 250m + memory: 512Mi + limits: + cpu: "1" + memory: 1Gi + volumes: + - name: data + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: rabbitmq + labels: + app.kubernetes.io/name: rabbitmq +spec: + selector: + app.kubernetes.io/name: rabbitmq + ports: + - name: amqp + port: 5672 + targetPort: amqp + - name: management + port: 15672 + targetPort: management +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: easyai-pgvector + labels: + app.kubernetes.io/name: easyai-pgvector +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: easyai-pgvector + template: + metadata: + labels: + app.kubernetes.io/name: easyai-pgvector + spec: + containers: + - name: postgres + image: registry.cn-shanghai.aliyuncs.com/easyaigc/pgvector:0.8.2-pg18-trixie + imagePullPolicy: IfNotPresent + ports: + - name: postgres + containerPort: 5432 + env: + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: easyai-app-secret + key: ASG_POSTGRES_USER + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: easyai-app-secret + key: ASG_POSTGRES_PASSWORD + - name: POSTGRES_DB + valueFrom: + secretKeyRef: + name: easyai-app-secret + key: ASG_POSTGRES_DB + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + volumeMounts: + - name: data + mountPath: /var/lib/postgresql/data + - name: init + mountPath: /docker-entrypoint-initdb.d/02-init-pgvector.sql + subPath: 02-init-pgvector.sql + readOnly: true + readinessProbe: + exec: + command: + - /bin/sh + - -c + - pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB" + initialDelaySeconds: 15 + periodSeconds: 10 + livenessProbe: + exec: + command: + - /bin/sh + - -c + - pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB" + initialDelaySeconds: 60 + periodSeconds: 30 + resources: + requests: + cpu: 250m + memory: 512Mi + limits: + cpu: "1" + memory: 1Gi + volumes: + - name: data + emptyDir: {} + - name: init + configMap: + name: pgvector-init +--- +apiVersion: v1 +kind: Service +metadata: + name: easyai-pgvector + labels: + app.kubernetes.io/name: easyai-pgvector +spec: + selector: + app.kubernetes.io/name: easyai-pgvector + ports: + - name: postgres + port: 5432 + targetPort: postgres diff --git a/k8s/overlays/local/kustomization.yaml b/k8s/overlays/local/kustomization.yaml new file mode 100644 index 0000000..42b488e --- /dev/null +++ b/k8s/overlays/local/kustomization.yaml @@ -0,0 +1,48 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: easyai-local + +resources: + - namespace.yaml + - ../../base + - app-config.yaml + - app-secret.yaml + - dependencies.yaml + +patches: + - target: + kind: Deployment + name: easyai-web + patch: |- + - op: replace + path: /spec/replicas + value: 2 + - target: + kind: Deployment + name: easyai-server + patch: |- + - op: replace + path: /spec/replicas + value: 2 + - target: + kind: Deployment + name: ws-gateway + patch: |- + - op: replace + path: /spec/replicas + value: 1 + - target: + kind: Deployment + name: easyai-asg + patch: |- + - op: replace + path: /spec/replicas + value: 1 + - target: + kind: Deployment + name: agent-memory + patch: |- + - op: replace + path: /spec/replicas + value: 1 diff --git a/k8s/overlays/local/namespace.yaml b/k8s/overlays/local/namespace.yaml new file mode 100644 index 0000000..8f073a9 --- /dev/null +++ b/k8s/overlays/local/namespace.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: easyai-local + labels: + app.kubernetes.io/name: easyai-local + app.kubernetes.io/part-of: easyai diff --git a/k8s/overlays/production/app-config.yaml b/k8s/overlays/production/app-config.yaml new file mode 100644 index 0000000..459ecfe --- /dev/null +++ b/k8s/overlays/production/app-config.yaml @@ -0,0 +1,91 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: easyai-app-config +data: + NUXT_PUBLIC_BASE_APIURL: /api + NUXT_PUBLIC_BASE_SOCKETURL: wss://easyai.example.com/socket.io + NUXT_PUBLIC_SG_APIURL: /asg-api + NUXT_APP_CDN_URL: "" + + MALLOC_ARENA_MAX: "2" + MALLOC_TRIM_THRESHOLD_: "131072" + MALLOC_MMAP_THRESHOLD_: "131072" + MALLOC_TOP_PAD_: "131072" + SHARP_CACHE_ENABLED: "false" + SHARP_CONCURRENCY: "1" + + CONFIG_COMFYUI_QUENE_REDIS_HOST: CHANGE_ME_REDIS_HOST + CONFIG_COMFYUI_QUENE_REDIS_PORT: "6379" + CONFIG_COMFYUI_QUENE_REDIS_USERNAME: "" + CONFIG_COMFYUI_QUENE_REDIS_DB: "6" + CONFIG_COMFYUI_CACHE_REDIS_DB: "11" + + LOG_LEVEL: log,error,warn + WS_GATEWAY_LOG_LEVEL: log,error,warn + CONFIG_TOKEN_EXPIRE: "1800" + CONFIG_PROXY_URL: "" + CONFIG_DOCS_DISABLE: "true" + + CONFIG_MQ_PROTOCOL: amqp + CONFIG_MQ_HOST: CHANGE_ME_RABBITMQ_HOST + CONFIG_MQ_PORT: "5672" + CONFIG_MQ_VHOST: / + + CONFIG_TCP_HOST: 0.0.0.0 + CONFIG_TCP_PORT: "4002" + GATEWAY_CLUSTER_REDIS_ENABLED: "true" + GATEWAY_CLUSTER_REDIS_CHANNEL: easyai:wsgateway:cluster + GATEWAY_CLUSTER_REDIS_ROUTE_PREFIX: easyai:wsgateway:route + GATEWAY_CLUSTER_REDIS_ROUTE_TTL_SEC: "120" + GATEWAY_CLUSTER_REDIS_RECONNECT_INTERVAL_MS: "5000" + GATEWAY_CLUSTER_REDIS_URL: "" + GATEWAY_CLUSTER_REDIS_HOST: CHANGE_ME_REDIS_HOST + GATEWAY_CLUSTER_REDIS_PORT: "6379" + GATEWAY_CLUSTER_REDIS_USERNAME: "" + GATEWAY_CLUSTER_REDIS_DB: "0" + WS_GATEWAY_TCP_EVENT_PATTERN: gateway.event.publish + WS_GATEWAY_TCP_TIMEOUT_MS: "1500" + GATEWAY_INBOUND_TCP_PATTERN: gateway.inbound.command + GATEWAY_INBOUND_TIMEOUT_MS: "5000" + + WS_AUTH_REQUIRED: "false" + WS_AUTH_TIMEOUT_MS: "6000" + WS_AUTH_METHODS: none,bearer + WS_AUTH_WS_TICKETS: "" + + ASG_REDIS_HOST: CHANGE_ME_REDIS_HOST + ASG_REDIS_PORT: "6379" + ASG_REDIS_DB: "8" + ASG_PORT: "3003" + ASG_TCP_HOST: 0.0.0.0 + ASG_TCP_PORT: "4003" + ASG_DOCS_DISABLE: "true" + ASG_MAIN_BACKEND_URL: http://easyai-server:3001 + + AMS_PORT: "3004" + MEMORY_AI_BASE_URL: http://easyai-server:3001 + MEMORY_EMBEDDINGS_PATH: /v1/embeddings + MEMORY_EMBEDDING_MODELS_PATH: /v1/embeddings/models + MEMORY_EMBEDDING_DIMENSION: "1024" + MEMORY_EMBEDDING_MODEL: Qwen3-Embedding-v4 + MEMORY_SCORE_CONFIDENCE_BASE: "0.6" + MEMORY_SCORE_CONFIDENCE_GAIN: "0.4" + MEMORY_SCORE_IMPORTANCE_BASE: "0.7" + MEMORY_SCORE_IMPORTANCE_GAIN: "0.3" + MEMORY_DEDUP_MIN_SCORE: "0.92" + MEMORY_TEXT_DEDUP_MIN_SCORE: "0.9" + MEMORY_CHAT_COMPLETIONS_PATH: /v1/chat/completions + MEMORY_CHAT_MODELS_PATH: /v1/models + MEMORY_CHAT_MODEL: qwen-plus + MEMORY_COMPRESSION_ENABLED: "true" + MEMORY_COMPRESSION_MIN_LENGTH: "80" + MEMORY_MAINTENANCE_ENABLED: "true" + MEMORY_MAINTENANCE_DRY_RUN: "false" + MEMORY_MAINTENANCE_BATCH_SIZE: "200" + MEMORY_MAINTENANCE_LLM_ENABLED: "true" + MEMORY_MAINTENANCE_LLM_MAX_PAIRS: "20" + MEMORY_DOCS_DISABLE: "true" + + OSS_REGION: us-east-1 + OSS_DOMAIN: "" diff --git a/k8s/overlays/production/app-secret.yaml b/k8s/overlays/production/app-secret.yaml new file mode 100644 index 0000000..2141cc4 --- /dev/null +++ b/k8s/overlays/production/app-secret.yaml @@ -0,0 +1,38 @@ +apiVersion: v1 +kind: Secret +metadata: + name: easyai-app-secret +type: Opaque +stringData: + CONFIG_DB_MONGO_URI: mongodb://CHANGE_ME_MONGO_HOST:27017 + CONFIG_DB_MONGO_USERNAME: CHANGE_ME_MONGO_USERNAME + CONFIG_DB_MONGO_PASSWORD: CHANGE_ME_MONGO_PASSWORD + MONGO_INITDB_ROOT_USERNAME: CHANGE_ME_MONGO_USERNAME + MONGO_INITDB_ROOT_PASSWORD: CHANGE_ME_MONGO_PASSWORD + + CONFIG_COMFYUI_QUENE_REDIS_PASSWORD: CHANGE_ME_REDIS_PASSWORD + GATEWAY_CLUSTER_REDIS_PASSWORD: CHANGE_ME_REDIS_PASSWORD + ASG_REDIS_PASSWORD: CHANGE_ME_REDIS_PASSWORD + + CONFIG_MQ_USER: CHANGE_ME_RABBITMQ_USERNAME + CONFIG_MQ_PASSWORD: CHANGE_ME_RABBITMQ_PASSWORD + + CONFIG_JWT_SECRET: CHANGE_ME_LONG_RANDOM_JWT_SECRET + CONFIG_TOKEN_SIGN_SK: CHANGE_ME_LONG_RANDOM_TOKEN_SIGN_KEY + WS_AUTH_BEARER_TOKENS: "" + + ASG_DATABASE_URL: postgresql://CHANGE_ME_POSTGRES_USER:CHANGE_ME_POSTGRES_PASSWORD@CHANGE_ME_POSTGRES_HOST:5432/agent_governance?schema=public + MEMORY_DATABASE_URL: postgresql://CHANGE_ME_POSTGRES_USER:CHANGE_ME_POSTGRES_PASSWORD@CHANGE_ME_POSTGRES_HOST:5432/easyai_memory?schema=public + ASG_POSTGRES_USER: CHANGE_ME_POSTGRES_USER + ASG_POSTGRES_PASSWORD: CHANGE_ME_POSTGRES_PASSWORD + ASG_POSTGRES_DB: agent_governance + ASG_ADMIN_USERNAME: admin + ASG_ADMIN_PASSWORD: CHANGE_ME_EASYAI_ADMIN_PASSWORD + MEMORY_AI_API_KEY: "" + + SANDBOX_JUPYTER_TOKEN: CHANGE_ME_DISABLE_PUBLIC_JUPYTER_OR_SET_TOKEN + + OSS_ENDPOINT: "" + OSS_ACCESS_KEY_ID: "" + OSS_ACCESS_KEY_SECRET: "" + OSS_BUCKET: "" diff --git a/k8s/overlays/production/hpa.yaml b/k8s/overlays/production/hpa.yaml new file mode 100644 index 0000000..d7c47d9 --- /dev/null +++ b/k8s/overlays/production/hpa.yaml @@ -0,0 +1,137 @@ +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: easyai-web +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: easyai-web + minReplicas: 2 + maxReplicas: 10 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 70 + behavior: + scaleDown: + stabilizationWindowSeconds: 300 +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: easyai-server +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: easyai-server + minReplicas: 2 + maxReplicas: 20 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 70 + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: 75 + behavior: + scaleDown: + stabilizationWindowSeconds: 300 +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: ws-gateway +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: ws-gateway + minReplicas: 2 + maxReplicas: 10 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 70 + behavior: + scaleDown: + stabilizationWindowSeconds: 300 +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: video-edit +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: video-edit + minReplicas: 1 + maxReplicas: 5 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 70 + behavior: + scaleDown: + stabilizationWindowSeconds: 600 +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: easyai-asg +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: easyai-asg + minReplicas: 2 + maxReplicas: 8 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 70 + behavior: + scaleDown: + stabilizationWindowSeconds: 300 +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: agent-memory +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: agent-memory + minReplicas: 2 + maxReplicas: 8 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 70 + behavior: + scaleDown: + stabilizationWindowSeconds: 300 diff --git a/k8s/overlays/production/ingress.yaml b/k8s/overlays/production/ingress.yaml new file mode 100644 index 0000000..6376585 --- /dev/null +++ b/k8s/overlays/production/ingress.yaml @@ -0,0 +1,126 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: easyai-web + annotations: + nginx.ingress.kubernetes.io/proxy-read-timeout: "300" + nginx.ingress.kubernetes.io/proxy-send-timeout: "300" +spec: + ingressClassName: nginx + tls: + - hosts: + - easyai.example.com + secretName: easyai-tls + rules: + - host: easyai.example.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: easyai-web + port: + number: 3010 +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: easyai-api + annotations: + nginx.ingress.kubernetes.io/use-regex: "true" + nginx.ingress.kubernetes.io/rewrite-target: /$2 + nginx.ingress.kubernetes.io/proxy-body-size: 100m + nginx.ingress.kubernetes.io/proxy-buffering: "off" + nginx.ingress.kubernetes.io/proxy-read-timeout: "1200" + nginx.ingress.kubernetes.io/proxy-send-timeout: "1200" +spec: + ingressClassName: nginx + tls: + - hosts: + - easyai.example.com + secretName: easyai-tls + rules: + - host: easyai.example.com + http: + paths: + - path: /api(/|$)(.*) + pathType: ImplementationSpecific + backend: + service: + name: easyai-server + port: + number: 3001 + - path: /asg-api(/|$)(.*) + pathType: ImplementationSpecific + backend: + service: + name: easyai-asg + port: + number: 3003 + - path: /ams-api(/|$)(.*) + pathType: ImplementationSpecific + backend: + service: + name: agent-memory + port: + number: 3004 + - path: /sandbox(/|$)(.*) + pathType: ImplementationSpecific + backend: + service: + name: sandbox + port: + number: 8000 +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: easyai-ws + annotations: + nginx.ingress.kubernetes.io/proxy-buffering: "off" + nginx.ingress.kubernetes.io/proxy-read-timeout: "300" + nginx.ingress.kubernetes.io/proxy-send-timeout: "300" +spec: + ingressClassName: nginx + tls: + - hosts: + - easyai.example.com + secretName: easyai-tls + rules: + - host: easyai.example.com + http: + paths: + - path: /socket.io + pathType: Prefix + backend: + service: + name: ws-gateway + port: + number: 3002 +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: easyai-mcp + annotations: + nginx.ingress.kubernetes.io/proxy-buffering: "off" + nginx.ingress.kubernetes.io/proxy-read-timeout: "300" + nginx.ingress.kubernetes.io/proxy-send-timeout: "300" +spec: + ingressClassName: nginx + tls: + - hosts: + - easyai.example.com + secretName: easyai-tls + rules: + - host: easyai.example.com + http: + paths: + - path: /mcp + pathType: Prefix + backend: + service: + name: easyai-server + port: + number: 3001 diff --git a/k8s/overlays/production/kustomization.yaml b/k8s/overlays/production/kustomization.yaml new file mode 100644 index 0000000..282b743 --- /dev/null +++ b/k8s/overlays/production/kustomization.yaml @@ -0,0 +1,107 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: easyai + +resources: + - namespace.yaml + - ../../base + - app-config.yaml + - app-secret.yaml + - shared-pvc.yaml + - ingress.yaml + - hpa.yaml + - pdb.yaml + +images: + - name: registry.cn-shanghai.aliyuncs.com/comfy-ai/one-ai + newTag: latest + - name: registry.cn-shanghai.aliyuncs.com/comfy-ai/comfy-server + newTag: latest + - name: registry.cn-shanghai.aliyuncs.com/easyaigc/wsgateway + newTag: latest + - name: registry.cn-shanghai.aliyuncs.com/easyaigc/videoedit + newTag: latest + - name: registry.cn-shanghai.aliyuncs.com/easyaigc/sandbox + newTag: latest + - name: registry.cn-shanghai.aliyuncs.com/easyaigc/agent-governance + newTag: latest + - name: registry.cn-shanghai.aliyuncs.com/easyaigc/agent-memory + newTag: latest + +patches: + - target: + kind: Deployment + name: easyai-server + patch: |- + - op: replace + path: /spec/template/spec/volumes/0 + value: + name: shared-files + persistentVolumeClaim: + claimName: easyai-shared-files + - target: + kind: Deployment + name: easyai-web + patch: |- + - op: add + path: /spec/template/spec/topologySpreadConstraints + value: + - maxSkew: 1 + topologyKey: kubernetes.io/hostname + whenUnsatisfiable: ScheduleAnyway + labelSelector: + matchLabels: + app.kubernetes.io/name: easyai-web + - target: + kind: Deployment + name: easyai-server + patch: |- + - op: add + path: /spec/template/spec/topologySpreadConstraints + value: + - maxSkew: 1 + topologyKey: kubernetes.io/hostname + whenUnsatisfiable: ScheduleAnyway + labelSelector: + matchLabels: + app.kubernetes.io/name: easyai-server + - target: + kind: Deployment + name: ws-gateway + patch: |- + - op: add + path: /spec/template/spec/topologySpreadConstraints + value: + - maxSkew: 1 + topologyKey: kubernetes.io/hostname + whenUnsatisfiable: ScheduleAnyway + labelSelector: + matchLabels: + app.kubernetes.io/name: ws-gateway + - target: + kind: Deployment + name: easyai-asg + patch: |- + - op: add + path: /spec/template/spec/topologySpreadConstraints + value: + - maxSkew: 1 + topologyKey: kubernetes.io/hostname + whenUnsatisfiable: ScheduleAnyway + labelSelector: + matchLabels: + app.kubernetes.io/name: easyai-asg + - target: + kind: Deployment + name: agent-memory + patch: |- + - op: add + path: /spec/template/spec/topologySpreadConstraints + value: + - maxSkew: 1 + topologyKey: kubernetes.io/hostname + whenUnsatisfiable: ScheduleAnyway + labelSelector: + matchLabels: + app.kubernetes.io/name: agent-memory diff --git a/k8s/overlays/production/namespace.yaml b/k8s/overlays/production/namespace.yaml new file mode 100644 index 0000000..f2847cb --- /dev/null +++ b/k8s/overlays/production/namespace.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: easyai + labels: + app.kubernetes.io/name: easyai + app.kubernetes.io/part-of: easyai diff --git a/k8s/overlays/production/pdb.yaml b/k8s/overlays/production/pdb.yaml new file mode 100644 index 0000000..80ec477 --- /dev/null +++ b/k8s/overlays/production/pdb.yaml @@ -0,0 +1,49 @@ +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: easyai-web +spec: + minAvailable: 1 + selector: + matchLabels: + app.kubernetes.io/name: easyai-web +--- +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: easyai-server +spec: + minAvailable: 1 + selector: + matchLabels: + app.kubernetes.io/name: easyai-server +--- +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: ws-gateway +spec: + minAvailable: 1 + selector: + matchLabels: + app.kubernetes.io/name: ws-gateway +--- +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: easyai-asg +spec: + minAvailable: 1 + selector: + matchLabels: + app.kubernetes.io/name: easyai-asg +--- +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: agent-memory +spec: + minAvailable: 1 + selector: + matchLabels: + app.kubernetes.io/name: agent-memory diff --git a/k8s/overlays/production/shared-pvc.yaml b/k8s/overlays/production/shared-pvc.yaml new file mode 100644 index 0000000..a030b1b --- /dev/null +++ b/k8s/overlays/production/shared-pvc.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: easyai-shared-files + labels: + app.kubernetes.io/name: easyai-shared-files +spec: + accessModes: + - ReadWriteMany + storageClassName: CHANGE_ME_RWX_STORAGE_CLASS + resources: + requests: + storage: 100Gi