构建:新增完整容器化部署
This commit is contained in:
22
.env.example
Normal file
22
.env.example
Normal file
@@ -0,0 +1,22 @@
|
||||
# 复制为 .env 后必须替换以下三个敏感值;不要把真实 .env 提交到 Git。
|
||||
POSTGRES_PASSWORD=replace-with-a-strong-database-password
|
||||
APP_MASTER_KEY=replace-with-at-least-32-random-characters
|
||||
APP_ADMIN_PASSWORD=replace-with-a-strong-admin-password
|
||||
|
||||
# 百炼知识库未启用时可以保留为空;模型 API Key 仍通过页面配置并加密保存。
|
||||
DASHSCOPE_API_KEY=
|
||||
|
||||
# 非敏感运行参数。
|
||||
POSTGRES_DB=smart_factory_agent
|
||||
POSTGRES_USER=smart_factory
|
||||
APP_ADMIN_USERNAME=admin
|
||||
APP_RUN_TIMEOUT=60m
|
||||
SESSION_COOKIE_SECURE=false
|
||||
MANUAGENT_DATA_ROOT=/srv/manuagent/data
|
||||
# 默认值即部署要求的 0.0.0.0:5173;普通 Linux 部署无需修改。
|
||||
FRONTEND_PORT=5173
|
||||
|
||||
# 可选镜像标签,便于私有镜像仓库或版本升级时覆盖。
|
||||
AGENT_RUNTIME_IMAGE=smart-factory-agent-runtime:0.1.0
|
||||
BACKEND_IMAGE=manuagent-backend:0.1.0
|
||||
FRONTEND_IMAGE=manuagent-frontend:0.1.0
|
||||
6
.gitattributes
vendored
Normal file
6
.gitattributes
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
# 容器内执行的 Shell 脚本必须使用 LF,避免 Windows 检出时生成 CRLF 导致解释器无法识别。
|
||||
*.sh text eol=lf
|
||||
|
||||
# Docker 与 Nginx 配置统一使用 LF,便于在 Linux 容器内直接加载。
|
||||
Dockerfile text eol=lf
|
||||
*.conf text eol=lf
|
||||
53
README.md
53
README.md
@@ -11,14 +11,14 @@
|
||||
## 本地启动
|
||||
|
||||
```bash
|
||||
docker compose --profile build-only build agent-runtime
|
||||
docker compose -f docker-compose.yml build agent-runtime
|
||||
docker compose up -d postgres
|
||||
mvn -q -f server/pom.xml spring-boot:run
|
||||
npm --prefix client install
|
||||
npm --prefix client run dev
|
||||
npm --prefix web-ui install
|
||||
npm --prefix web-ui run dev
|
||||
```
|
||||
|
||||
打开 <http://127.0.0.1:5173>,本地默认账号为 `admin / admin123`。
|
||||
本地开发服务器默认使用 Vite 配置的端口。容器化部署固定从 <http://127.0.0.1:5173> 访问。
|
||||
|
||||
模型连接只能在“模型配置”页面新增并持久化到 PostgreSQL;API Key 会使用 `APP_MASTER_KEY`
|
||||
环境变量提供的主密钥加密后保存。`dashscope_key.txt` 仅供百炼知识库使用,不参与模型配置。
|
||||
@@ -29,12 +29,53 @@ npm --prefix client run dev
|
||||
$env:APP_MASTER_KEY = '<使用独立生成的高强度密钥>'
|
||||
```
|
||||
|
||||
## Docker Compose 部署
|
||||
|
||||
部署包含 Nginx 前端、Spring Boot 后端、Agent Runtime 镜像和 PostgreSQL。Agent Runtime
|
||||
不是常驻 API 服务:`agent-runtime` 服务完成镜像构建后会以状态码 0 退出,后端再通过 Docker
|
||||
Socket 为每个 Agent Session 动态创建隔离容器。
|
||||
|
||||
1. 复制环境变量模板并替换其中的三个必填密码/密钥;生产环境建议使用密码管理系统生成随机值。
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
2. 确认 `MANUAGENT_DATA_ROOT` 是 Docker 宿主机上的绝对路径。这个路径会以完全相同的路径挂载到
|
||||
后端容器,供动态 Agent Runtime 继续挂载项目材料、工作目录和产物。Linux 默认值为
|
||||
`/srv/manuagent/data`。
|
||||
|
||||
3. 构建并启动完整服务:
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.yml up -d --build
|
||||
docker compose -f docker-compose.yml ps
|
||||
```
|
||||
|
||||
4. 打开 <http://127.0.0.1:5173>,使用 `.env` 中的 `APP_ADMIN_USERNAME` 和
|
||||
`APP_ADMIN_PASSWORD` 登录。查看日志或停止服务:
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.yml logs -f backend
|
||||
docker compose -f docker-compose.yml down
|
||||
```
|
||||
|
||||
默认端口映射为 `0.0.0.0:5173:80`。若 Windows 的动态端口排除范围占用了 5173,可仅在本机
|
||||
验证时临时执行 `$env:FRONTEND_PORT = '15173'`;Linux 服务器部署应保留默认的 5173。
|
||||
|
||||
PostgreSQL 数据保存在 `postgres-data` 命名卷中;项目材料、Agent 工作区、快照和生成文件保存在
|
||||
`MANUAGENT_DATA_ROOT`。`docker compose down` 不会删除它们,只有显式增加 `--volumes` 才会删除
|
||||
PostgreSQL 卷。后端挂载 Docker Socket 等价于授予其管理宿主机容器的高权限,应只在受信任的
|
||||
Docker 主机上运行,并限制 5173 端口的网络访问范围。通过 HTTPS 反向代理部署时,请将
|
||||
`SESSION_COOKIE_SECURE` 设为 `true`。
|
||||
|
||||
## 验证
|
||||
|
||||
```bash
|
||||
mvn -q -f server/pom.xml test
|
||||
npm --prefix client run test -- --run
|
||||
npm --prefix client run build
|
||||
npm --prefix web-ui run test
|
||||
npm --prefix web-ui run build
|
||||
docker compose -f docker-compose.yml config --quiet
|
||||
```
|
||||
|
||||
产品、数据库与验收设计见 [docs](docs/)。内置 Skill 与资源由 Flyway 种子迁移写入数据库,无需额外导入文件。
|
||||
|
||||
25
compose.yml
25
compose.yml
@@ -1,25 +0,0 @@
|
||||
services:
|
||||
agent-runtime:
|
||||
image: smart-factory-agent-runtime:0.1.0
|
||||
build:
|
||||
context: sandbox
|
||||
profiles: ["build-only"]
|
||||
|
||||
postgres:
|
||||
image: postgres:17-alpine
|
||||
environment:
|
||||
POSTGRES_DB: smart_factory_agent
|
||||
POSTGRES_USER: smart_factory
|
||||
POSTGRES_PASSWORD: smart_factory
|
||||
ports:
|
||||
- "127.0.0.1:54330:5432"
|
||||
volumes:
|
||||
- smart-factory-pg:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U smart_factory -d smart_factory_agent"]
|
||||
interval: 3s
|
||||
timeout: 3s
|
||||
retries: 20
|
||||
|
||||
volumes:
|
||||
smart-factory-pg:
|
||||
131
docker-compose.yml
Normal file
131
docker-compose.yml
Normal file
@@ -0,0 +1,131 @@
|
||||
name: manuagent
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:17-alpine
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_DB: ${POSTGRES_DB:-smart_factory_agent}
|
||||
POSTGRES_USER: ${POSTGRES_USER:-smart_factory}
|
||||
POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password
|
||||
secrets:
|
||||
- postgres_password
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
networks:
|
||||
- manuagent-network
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 20
|
||||
start_period: 10s
|
||||
|
||||
# 此服务负责构建并登记 AgentScope 使用的 Runtime 镜像。
|
||||
# 它成功退出后,后端会通过 Docker Socket 按 Session 动态创建真正执行任务的 Runtime 容器。
|
||||
agent-runtime:
|
||||
image: ${AGENT_RUNTIME_IMAGE:-smart-factory-agent-runtime:0.1.0}
|
||||
build:
|
||||
context: ./sandbox
|
||||
dockerfile: Dockerfile
|
||||
command: ["/bin/true"]
|
||||
restart: "no"
|
||||
networks:
|
||||
- manuagent-network
|
||||
|
||||
backend:
|
||||
image: ${BACKEND_IMAGE:-manuagent-backend:0.1.0}
|
||||
build:
|
||||
context: ./server
|
||||
dockerfile: Dockerfile
|
||||
restart: unless-stopped
|
||||
init: true
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
restart: true
|
||||
agent-runtime:
|
||||
condition: service_completed_successfully
|
||||
environment:
|
||||
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/${POSTGRES_DB:-smart_factory_agent}
|
||||
SPRING_DATASOURCE_USERNAME: ${POSTGRES_USER:-smart_factory}
|
||||
# Spring Boot 将 secrets 目录中的点分文件名作为配置属性加载,避免把密码写进普通环境变量。
|
||||
SPRING_CONFIG_IMPORT: optional:configtree:/run/secrets/
|
||||
APP_DATA_ROOT: ${MANUAGENT_DATA_ROOT:-/srv/manuagent/data}
|
||||
APP_DASHSCOPE_KEY_FILE: /run/secrets/dashscope_key
|
||||
APP_ADMIN_USERNAME: ${APP_ADMIN_USERNAME:-admin}
|
||||
APP_SANDBOX_IMAGE: ${AGENT_RUNTIME_IMAGE:-smart-factory-agent-runtime:0.1.0}
|
||||
APP_SANDBOX_NETWORK: manuagent-network
|
||||
APP_RUN_TIMEOUT: ${APP_RUN_TIMEOUT:-60m}
|
||||
SERVER_SERVLET_SESSION_COOKIE_SECURE: ${SESSION_COOKIE_SECURE:-false}
|
||||
secrets:
|
||||
- source: postgres_password
|
||||
target: spring.datasource.password
|
||||
- source: app_master_key
|
||||
target: app.master-key
|
||||
- source: admin_password
|
||||
target: app.admin-password
|
||||
- source: dashscope_api_key
|
||||
target: dashscope_key
|
||||
volumes:
|
||||
# DockerSandbox 的 bind mount 源路径由宿主机 daemon 解释,因此容器内外必须使用相同绝对路径。
|
||||
- type: bind
|
||||
source: ${MANUAGENT_DATA_ROOT:-/srv/manuagent/data}
|
||||
target: ${MANUAGENT_DATA_ROOT:-/srv/manuagent/data}
|
||||
# AgentScope 需要通过宿主机 Docker Engine 创建、执行并销毁隔离的 Runtime 容器。
|
||||
- type: bind
|
||||
source: /var/run/docker.sock
|
||||
target: /var/run/docker.sock
|
||||
expose:
|
||||
- "8080"
|
||||
networks:
|
||||
- manuagent-network
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "--fail", "--silent", "--show-error", "http://127.0.0.1:8080/api/auth/csrf"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 18
|
||||
start_period: 30s
|
||||
stop_grace_period: 30s
|
||||
|
||||
frontend:
|
||||
image: ${FRONTEND_IMAGE:-manuagent-frontend:0.1.0}
|
||||
build:
|
||||
context: ./web-ui
|
||||
dockerfile: Dockerfile
|
||||
restart: unless-stopped
|
||||
init: true
|
||||
depends_on:
|
||||
backend:
|
||||
condition: service_healthy
|
||||
restart: true
|
||||
ports:
|
||||
# 默认严格监听 0.0.0.0:5173;仅当宿主机保留该端口时,才通过 FRONTEND_PORT 临时覆盖。
|
||||
- "0.0.0.0:${FRONTEND_PORT:-5173}:80"
|
||||
networks:
|
||||
- manuagent-network
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--quiet", "--output-document=/dev/null", "http://127.0.0.1/"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 6
|
||||
start_period: 10s
|
||||
|
||||
networks:
|
||||
manuagent-network:
|
||||
# 固定网络名,确保后端动态创建的 Agent Runtime 容器可以加入同一个网络。
|
||||
name: manuagent-network
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
|
||||
secrets:
|
||||
postgres_password:
|
||||
environment: POSTGRES_PASSWORD
|
||||
app_master_key:
|
||||
environment: APP_MASTER_KEY
|
||||
admin_password:
|
||||
environment: APP_ADMIN_PASSWORD
|
||||
dashscope_api_key:
|
||||
environment: DASHSCOPE_API_KEY
|
||||
6
server/.dockerignore
Normal file
6
server/.dockerignore
Normal file
@@ -0,0 +1,6 @@
|
||||
target
|
||||
.idea
|
||||
*.iml
|
||||
*.log
|
||||
.env
|
||||
.env.*
|
||||
37
server/Dockerfile
Normal file
37
server/Dockerfile
Normal file
@@ -0,0 +1,37 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
# Maven 构建阶段使用与项目一致的 JDK 21,并利用 BuildKit 缓存减少重复下载依赖的时间。
|
||||
FROM maven:3.9.13-eclipse-temurin-21 AS builder
|
||||
|
||||
WORKDIR /workspace
|
||||
COPY pom.xml ./
|
||||
COPY src ./src
|
||||
# 直接打包只解析项目真正需要的依赖;独立 go-offline 会额外下载大量未参与构建的报告插件。
|
||||
RUN --mount=type=cache,target=/root/.m2 mvn -B -DskipTests package
|
||||
|
||||
# AgentScope DockerSandbox 通过 docker 命令创建 Runtime,直接复用官方镜像中的 CLI 二进制。
|
||||
FROM docker:29-cli AS docker-cli
|
||||
|
||||
FROM eclipse-temurin:21-jre-jammy
|
||||
|
||||
# curl 用于容器健康检查;gosu 用于完成目录和 Docker Socket 权限初始化后降权运行 Java。
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends ca-certificates curl gosu \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY --from=docker-cli /usr/local/bin/docker /usr/local/bin/docker
|
||||
|
||||
RUN groupadd --gid 10001 manuagent \
|
||||
&& useradd --uid 10001 --gid 10001 --create-home --shell /bin/bash manuagent \
|
||||
&& mkdir -p /opt/manuagent /srv/manuagent/data \
|
||||
&& chown -R manuagent:manuagent /opt/manuagent /srv/manuagent
|
||||
|
||||
WORKDIR /opt/manuagent
|
||||
COPY --from=builder /workspace/target/*.jar app.jar
|
||||
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
||||
RUN chmod 0755 /usr/local/bin/docker-entrypoint.sh
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
ENTRYPOINT ["docker-entrypoint.sh"]
|
||||
CMD ["java", "-XX:MaxRAMPercentage=75.0", "-Djava.security.egd=file:/dev/urandom", "-jar", "/opt/manuagent/app.jar"]
|
||||
25
server/docker-entrypoint.sh
Normal file
25
server/docker-entrypoint.sh
Normal file
@@ -0,0 +1,25 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
# Agent Runtime 的目录通过宿主机 Docker daemon 再次挂载,因此后端必须能写入共享数据根目录。
|
||||
data_root="${APP_DATA_ROOT:-/srv/manuagent/data}"
|
||||
mkdir -p "$data_root"
|
||||
chown manuagent:manuagent "$data_root"
|
||||
|
||||
# Docker Socket 的组 ID 在不同 Linux 发行版和 Docker Desktop 中并不固定。
|
||||
# 启动时读取真实组 ID 并把低权限应用用户加入对应组,避免以 root 身份运行 Spring Boot。
|
||||
docker_socket="/var/run/docker.sock"
|
||||
if [ ! -S "$docker_socket" ]; then
|
||||
echo "错误:未挂载 $docker_socket,后端无法创建 Agent Runtime 容器。" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
docker_gid="$(stat -c '%g' "$docker_socket")"
|
||||
docker_group="$(getent group "$docker_gid" | cut -d: -f1 || true)"
|
||||
if [ -z "$docker_group" ]; then
|
||||
docker_group="docker-host"
|
||||
groupadd --gid "$docker_gid" "$docker_group"
|
||||
fi
|
||||
usermod -aG "$docker_group" manuagent
|
||||
|
||||
exec gosu manuagent "$@"
|
||||
7
web-ui/.dockerignore
Normal file
7
web-ui/.dockerignore
Normal file
@@ -0,0 +1,7 @@
|
||||
node_modules
|
||||
dist
|
||||
client
|
||||
*.tsbuildinfo
|
||||
npm-debug.log
|
||||
.env
|
||||
.env.*
|
||||
21
web-ui/Dockerfile
Normal file
21
web-ui/Dockerfile
Normal file
@@ -0,0 +1,21 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
# 第一阶段只负责安装锁定依赖并生成 Vite 静态资源,避免把 Node.js 和源码带入运行镜像。
|
||||
FROM node:22-bookworm-slim AS builder
|
||||
|
||||
WORKDIR /workspace
|
||||
|
||||
# 先复制依赖清单以复用 Docker 构建缓存;只有依赖变化时才重新执行 npm ci。
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci
|
||||
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# 第二阶段使用精简 Nginx 提供静态页面,并把同源 /api 请求转发给后端服务。
|
||||
FROM nginx:1.29.8-alpine
|
||||
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
COPY --from=builder /workspace/dist /usr/share/nginx/html
|
||||
|
||||
EXPOSE 80
|
||||
33
web-ui/nginx.conf
Normal file
33
web-ui/nginx.conf
Normal file
@@ -0,0 +1,33 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
# 企业材料允许上传到 160 MB,与 Spring Boot 的请求上限保持一致。
|
||||
client_max_body_size 160m;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# 前后端保持同源,浏览器中的 Session Cookie、CSRF Token 与流式事件接口无需额外跨域配置。
|
||||
location /api/ {
|
||||
proxy_pass http://backend:8080;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# Agent 事件采用长连接流式返回,关闭代理缓冲后事件才能及时到达前端。
|
||||
proxy_buffering off;
|
||||
proxy_request_buffering off;
|
||||
proxy_cache off;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
add_header X-Accel-Buffering no;
|
||||
}
|
||||
|
||||
# Vue Router 使用 history 模式,未命中的前端路由统一回退到入口页面。
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user