# OCDP - Open Cloud Deployment Platform

OCDP 是一个面向 Kubernetes 的大模型推理部署平台。当前核心场景是：用户在页面选择 Harbor 中的 `vllm-serve` Helm Chart，填写实例名称、命名空间和 values 后，后端从 Harbor 拉取封装好的 OCI Helm Chart，并通过 Helm SDK 部署到已配置好的 Kubernetes 集群。

## 当前能力

- Registry 管理：保存 Harbor / OCI Registry 地址与凭据，敏感字段加密入库。
- Artifact 浏览：通过 Harbor v2.0 API 浏览当前凭据可见的项目、repositories 和 chart tags，避免依赖 `/v2/_catalog` 全局 catalog 权限。
- 一键部署：从前端发起实例创建，后端拉取 Chart 并在目标集群执行 Helm install/upgrade/uninstall。
- 集群管理：保存 Kubernetes API Server、CA、客户端证书或 token，用于后端连接集群。
- 实例管理：查看部署状态、Helm revision、Service/Ingress 入口信息。
- 认证：内置 JWT 登录，首次启动可通过 bootstrap 注入管理员账号。

## 技术栈

- 后端：Go 1.24，Gorilla Mux，Hexagonal Architecture，PostgreSQL，ORAS SDK，Helm SDK，Kubernetes client-go。
- 前端：React 18，TypeScript，Vite，TailwindCSS。
- 部署：Docker Compose，Nginx 静态文件与 `/api` 反向代理，PostgreSQL 持久化。

## 项目结构

```text
ocdp-go/
├── backend/                     # Go 后端
│   ├── cmd/api/                 # API 入口
│   ├── internal/adapter/input/  # HTTP REST handlers / DTO
│   ├── internal/adapter/output/ # PostgreSQL / ORAS / Helm / K8s 实现
│   ├── internal/domain/         # Entity / Repository interface / Service
│   └── internal/bootstrap/      # 首次启动数据注入
├── frontend/                    # React + Vite 前端
├── infra/nginx/                 # Nginx 网关配置和 TLS 证书
├── docker-compose.yml           # 本地完整部署：PostgreSQL + Backend + 前端 build + Nginx
├── backend/docker-compose.yml   # PostgreSQL + Backend + pgAdmin
├── Makefile                     # 推荐入口：install / run-2 / docker-dev / docker-down
└── tasks/                       # Agent 工作记录
```

## 后端部署链路

1. 前端调用 `POST /api/v1/clusters/{clusterId}/instances`，提交 `name`、`namespace`、`registryId`、`repository`、`tag` 和可选 `values`。
2. 后端 `InstanceService.CreateInstance` 校验集群、Registry 和实例名唯一性，创建 pending 记录。
3. Chart 浏览使用 Harbor v2.0 API；实际部署时后端使用 ORAS SDK 访问 Harbor，将指定 repository/tag 的 Helm Chart layer 下载到 `/tmp/charts/{chart}-{version}.tgz`。
4. 后端用数据库中保存的集群凭据生成临时 kubeconfig。
5. Helm SDK 加载本地 chart 包，并对目标集群执行 `install`；后续通过 Helm status 同步实例状态。
6. 删除、升级和回滚实例同样通过 Helm SDK 操作目标集群。

## 部署前准备

需要本机已安装：

- Docker
- Docker Compose v2 或更高版本
- Make，可选；没有 Make 时可直接执行 Compose 命令

根目录 `.env` 用于开发环境启动时注入端口、数据库、初始账号、Harbor 和 Kubernetes 集群。它是开发/测试 bootstrap 数据，不是长期配置中心；系统启动后建议在页面里维护 Registry 和 Cluster。不要提交真实 `.env`。

关键变量如下，实际值以你的 `.env` 为准：

```dotenv
# 登录账号 bootstrap
BOOTSTRAP_ADMIN_USER=admin
BOOTSTRAP_ADMIN_PASS=change-me
BOOTSTRAP_ADMIN_EMAIL=admin@example.com

# Harbor bootstrap
BOOTSTRAP_REGISTRY_NAME=harbor
BOOTSTRAP_REGISTRY_URL=https://harbor.example.com
BOOTSTRAP_REGISTRY_DESC=Harbor Registry
# 推荐使用 Harbor robot 账号，只授予目标项目 pull/read 权限
BOOTSTRAP_REGISTRY_ROBOT_USER='robot$project+ocdp'
BOOTSTRAP_REGISTRY_ROBOT_PASS='robot-token'

# 可选 fallback；未配置 ROBOT 变量时才会使用
BOOTSTRAP_REGISTRY_USER=admin-or-user
BOOTSTRAP_REGISTRY_PASS=change-me
BOOTSTRAP_REGISTRY_INSECURE=false

# Kubernetes 集群 bootstrap，名称列表用逗号分隔
BOOTSTRAP_CLUSTERS=cluster1,cluster2
BOOTSTRAP_CLUSTER_CLUSTER1_HOST=https://x.x.x.x:6443
BOOTSTRAP_CLUSTER_CLUSTER1_DESC=GPU Cluster 1
BOOTSTRAP_CLUSTER_CLUSTER1_CA=base64-ca-data
BOOTSTRAP_CLUSTER_CLUSTER1_CERT=base64-client-cert-data
BOOTSTRAP_CLUSTER_CLUSTER1_KEY=base64-client-key-data

# 如使用 token，可配置 TOKEN；CERT/KEY 可按实际鉴权方式留空
BOOTSTRAP_CLUSTER_CLUSTER2_HOST=https://x.x.x.x:6443
BOOTSTRAP_CLUSTER_CLUSTER2_TOKEN=token-value

# 服务端口，默认使用高位端口避免和本机其他项目冲突
WEB_HTTP_PORT=18080
WEB_HTTPS_PORT=18443
BACKEND_PORT=18081
POSTGRES_PORT=15432

# 安全与数据库
JWT_SECRET=replace-with-a-strong-secret
ENCRYPTION_KEY=replace-with-32-byte-key
POSTGRES_DB=ocdp
POSTGRES_USER=postgres
POSTGRES_PASSWORD=replace-me

# 可选：Docker 构建后端时使用的 Go module proxy。
# 国内网络建议保留默认值；如公司网络要求，也可改回 https://proxy.golang.org,direct。
GOPROXY=https://goproxy.cn,direct
GOSUMDB=sum.golang.google.cn
```

说明：

- `BOOTSTRAP_CONFIG_JSON` 优先级最高，适合把完整 bootstrap 配置作为 JSON 注入。
- 没有 `BOOTSTRAP_CONFIG_JSON` 时，后端会读取 `BOOTSTRAP_*` 变量生成初始账号、Registry 和 Cluster。
- 没有任何显式 bootstrap 配置时，后端不会预注入用户、Registry 或 Cluster；代码中不再保留真实 Harbor、admin 或集群 fallback。
- 初始管理员必须显式配置 `BOOTSTRAP_ADMIN_USER` 和 `BOOTSTRAP_ADMIN_PASS`。如果只配置 Registry/Cluster 而未配置管理员账号，系统不会自动创建默认账号。
- Registry bootstrap 凭据优先级为 `BOOTSTRAP_REGISTRY_ROBOT_USER/PASS`，然后才是 `BOOTSTRAP_REGISTRY_USER/PASS`。Harbor robot 账号需要能访问目标项目的 repositories 和 artifacts。
- Harbor robot 用户名通常包含 `$`。本项目 Compose 已使用 raw `env_file` 传给后端；如果你在 shell 里临时 `export BOOTSTRAP_REGISTRY_ROBOT_USER=...`，请用单引号包住值，避免 shell 展开 `$project`。
- 已存在同名用户、Registry 或 Cluster 时，bootstrap 会跳过，不会覆盖数据库里的记录。
- `ENCRYPTION_KEY` 用于加密保存 Harbor 密码和集群凭据；生产环境首次启动后不要随意更换，否则旧数据无法解密。

## 推荐部署流程

当前推荐使用根目录 Makefile。`docker-dev`、`docker-prod`、`docker-up` 都是兼容旧文档的别名，实际会启动同一套完整 Docker Compose 栈：PostgreSQL、Backend、前端静态构建和 Nginx。

```bash
# 1. 在根目录检查 .env
ls .env

# 2. 可选：安装本地依赖。只部署 Docker 栈时不是必须，但这个命令可用。
make install

# 3. 如果默认高位端口仍被其他项目占用，再临时换端口
export WEB_HTTP_PORT=18080
export WEB_HTTPS_PORT=18443
export BACKEND_PORT=18081
export POSTGRES_PORT=15432

# 4. 构建并后台启动完整栈
make run-2

# 兼容旧文档，也可以执行：
make docker-dev
make docker-prod

# 5. 查看服务
make docker-ps
```

访问地址：

- 前端入口：http://localhost:${WEB_HTTP_PORT:-18080}
- 后端健康检查：http://localhost:${BACKEND_PORT:-18081}/health
- Swagger UI：http://localhost:${BACKEND_PORT:-18081}/api/docs
- Nginx 健康检查：http://localhost:${WEB_HTTP_PORT:-18080}/healthz

没有 Make 时，直接用根目录 Compose 文件即可。注意要加 `--build`，因为后端镜像和前端静态资源需要构建：

```bash
docker compose up --build -d postgres backend nginx
docker compose ps
```

如果直接执行 `docker compose up`，Compose 也会使用同一个完整栈；但在代码或 Dockerfile 改动后建议显式加 `--build`，避免复用旧镜像。

## 验证部署

```bash
# 健康检查
curl http://localhost:${BACKEND_PORT:-18081}/health
curl http://localhost:${WEB_HTTP_PORT:-18080}/healthz

# 登录，返回 token。把 password 替换成 .env 里的 BOOTSTRAP_ADMIN_PASS。
curl -s -X POST http://localhost:${BACKEND_PORT:-18081}/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"<BOOTSTRAP_ADMIN_PASS>"}'

# 查看 bootstrap 是否生效，需要带 Bearer token
curl http://localhost:${BACKEND_PORT:-18081}/api/v1/registries \
  -H "Authorization: Bearer <token>"

curl http://localhost:${BACKEND_PORT:-18081}/api/v1/clusters \
  -H "Authorization: Bearer <token>"
```

页面验证：

1. 打开前端入口并登录。
2. 进入 Chart Browser，确认能看到 Harbor 中的 `vllm-serve` 或 nginx chart repository。当前默认只展示可部署 Helm chart。
3. 选择 chart tag，点击 Launch。
4. 选择目标集群、命名空间，填写实例名和 values。values 支持 schema 表单或 YAML；YAML 会在前端校验，并由后端解析为 Helm values map。
5. 提交后到实例页面查看状态；后端会异步安装并同步 Helm 状态。

命令行 smoke test：

```bash
# 只验证登录、Registry health、Harbor chart 浏览和 values schema
BASE_URL=http://localhost:${BACKEND_PORT:-18081}/api/v1 \
ADMIN_USER="${BOOTSTRAP_ADMIN_USER:-admin}" \
ADMIN_PASS="<BOOTSTRAP_ADMIN_PASS>" \
./test/current-platform-smoke.sh

# 允许真实部署时，会创建测试 release 并在结束后调用平台删除
RUN_DEPLOY_TEST=true \
TEST_NAMESPACE=ocdp-smoke \
TEST_RELEASE=ocdp-smoke-nginx \
BASE_URL=http://localhost:${BACKEND_PORT:-18081}/api/v1 \
ADMIN_PASS="<BOOTSTRAP_ADMIN_PASS>" \
./test/current-platform-smoke.sh
```

## 常用运维命令

```bash
# 查看日志
make docker-logs

# 重启后端
docker compose restart backend

# 如果后端容器被重建过，Nginx 可能仍缓存旧 upstream IP；只需重启本项目 Nginx
docker compose restart nginx

# 停止本项目服务，但保留数据卷
make docker-down

# 清理本项目容器和数据卷，谨慎使用
make clean-2
```

## 本地开发与测试

后端：

```bash
cd backend
go test ./...
go run cmd/api/main.go
```

前端：

```bash
cd frontend
npm ci
npm run build
```

Mock 后端仍可通过 `backend/docker-compose.yml` 的 `mock` profile 启动：

```bash
docker compose -f backend/docker-compose.yml --profile mock up -d backend-mock
```

## 注意事项

- 不要为了端口冲突停止其他项目；优先通过 `WEB_HTTP_PORT`、`WEB_HTTPS_PORT`、`BACKEND_PORT`、`POSTGRES_PORT` 换端口。当前默认端口已经是 `18080/18443/18081/15432`。
- 如果旧文档提到 `make docker-dev`、`make docker-prod`，现在这些命令仍可用，都会启动同一套 Docker 栈。
- 如果之前用旧配置启动失败过，PostgreSQL 卷里可能残留旧的加密数据，表现为 `/api/v1/clusters` 或 `/api/v1/registries` 解密失败。开发/重装环境可执行 `make clean-2 && make docker-dev` 重新初始化；生产环境不要直接删卷，应先备份数据库。
- `vllm-serve` 必须以 Helm Chart OCI artifact 的形式存在于 Harbor 中；后端会寻找 Helm Chart layer 并保存为 `.tgz`。
- Harbor 浏览使用 `/api/v2.0/projects`、project repositories 和 artifacts API。若 robot 账号无法列项目或 artifacts，页面会显示明确错误；请检查 Harbor 项目成员/robot 权限，而不是给普通用户开放全局 catalog。
- values YAML 已按 YAML 解析；顶层必须是 mapping，例如 `replicaCount: 1`。
- Nginx 默认同时监听 HTTP 和 HTTPS，证书位于 `infra/nginx/certs/`，生产环境应替换为正式证书。
- `make clean-2` 会删除本项目 Compose 卷，包括 PostgreSQL 数据；只想停服务时使用 `docker compose ... down --remove-orphans`。

## API 文档

- OpenAPI YAML：[backend/docs/openapi.yaml](./backend/docs/openapi.yaml)
- 运行后 Swagger UI：`/api/docs`
