Files
KF-OnlineJudge/deploy/README.md

168 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 本地开发依赖
开发环境用 Compose 提供数据库和消息队列。单机交付使用独立的受限服务,安装入口见
[单机交付说明](release/README.md)。管理员账号、题包导入、发布、下架和审计见
[题目维护运维说明](admin-problems.md)。
## 启动开发依赖
将 `.env.example` 复制为 `.env`,在仓库根目录运行:
```bash
make infra-config
make infra-up
make infra-down
```
`compose.dev.yml` 只启动 PostgreSQL 和 RabbitMQ,端口绑定 localhost。
`infra-down` 保留数据卷。API 存活检查和前端预览可以先独立启动。
不要在这个 Compose 或宿主机上运行学生代码,也不要将示例口令用于正式部署。
开发镜像固定 PostgreSQL 18.6 和 RabbitMQ 4.3.6;正式实验还需锁定镜像摘要。
配置检查只检查 Compose 结构。服务健康和备份恢复要在实际环境验证。
镜像用法见官方 [PostgreSQL](https://hub.docker.com/_/postgres) 和
[RabbitMQ](https://hub.docker.com/_/rabbitmq) 说明。
## 单机 helper 工厂
`kf-sandbox factory --config /etc/kf-judge/runtime.json` 启动 Root 工厂。
普通 Worker 连接配置中的 `state_root/factory/control.sock`。
安装 `deploy/kf-sandbox-factory.service` 示例前,管理员需核对固定二进制、不可写的
GNU 镜像与配置、Worker UID/GID,以及 Root 所有且与监督器 cgroup 分离的任务委派根。
工厂读回服务和任务预算,每次编译、测试点运行都经过完整隔离检查。
工厂无等待队列,只接收有界 run ID。槽位满、重复 ID 和未知字段立即拒绝。
每次请求创建全新的单线程 helper,继承已认证连接和工厂生命线。
额外控制帧、断连或生命线消失会终止任务树。ID 先进入持久记录,最多 4096 个,
跨重启继续拒绝重放。清理失败时保留身份租约并停止分配;恢复只处理已记录的
归属和 inode,未知资源保留并拒绝启动。
### 轮换记录周期
可信 Root 参数 `--max-requests` 设置本周期受理上限,范围 1 到 4096,默认 4096。
容量用尽后,先停止任务并核对回收,再由管理员维护记录周期。
`kf-sandbox rotate-factory-cycle --config ROOT_CONFIG --cycle-id ID` 在受限监督器作用域
中核对配置和镜像、取得工厂排他锁,确认任务、身份、捕获目录、委派 cgroup 均为空,
且控制 socket 已消失,才归档请求记录到 Root 私有 `factory/cycles/`。
归档文件和目录经 fsync 后清空当前记录,最多保留 64 个周期;历史 ID 继续用于拒绝重放。
正常启动、重启、寿命结束和容量用尽不会自动轮换或删除历史记录。
若归档完成后中断,先排空,再显式用同一 cycle ID 重试。未知或不完整记录需要检查,
不能删日志续用工厂。轮换入口不启动服务。新安装路径的轮换、崩溃恢复和跨周期拒绝
仍需独立 VM 验收。
## 单机提交服务的启动与停止
`kf-worker judge-submission` 是受控本地适配入口;常驻
`kf-worker serve --config` 使用有界 AMQP 通知和认证后的 Coordinator claim。
配置见[判题工程说明](../judge/README.md#单机持久化协调与常驻-worker)。
Worker 校验不可变清单摘要,将 C17/C++20 映射到固定 GNU 命令,编译一次。
产物经完整清理后封印并计算 SHA-256;每个测试点单独申请 helper、stdin 和工作区。
期望输出留在比较端。Worker 用 `sha2 0.10.9` 校验输入与产物;Sandbox 的同版本
开发依赖仅生成独立 VM 测试数据,Engine 不依赖加密或 OS 库。
### 启动顺序
1. 用独立迁移账号执行 Backend Alembic。平台与协调器登录账号分别继承各自的
NOLOGIN 角色;平台只插入初始评测,执行状态由 Coordinator 写入。
2. 为平台准备私有工件目录,通过应用服务导入不可变题目并创建测试账号。
3. 验证 GNU 镜像、Root 配置、工厂服务和任务 cgroup 的实际限额,启动工厂。
4. 独立启动 API、Coordinator、outbox publisher 和普通 Worker。HTTP/AMQP 使用
数值 loopback,PostgreSQL 使用 loopback/Unix socket;启动入口不创建表。
5. 检查 Coordinator 数据库权限与 readiness,再在独立 VM 验证执行隔离。
Root 工厂不持有平台/MQ/数据库口令,用户运行镜像不含服务文件。
平台配置包括私有 `KF_OJ_ARTIFACT_ROOT`、工件容量和可信
`KF_OJ_JUDGE_ADMISSION`。工具链、编译/运行预算、作业期限、并发/prefetch/retry
必须与 Coordinator/Worker 一致。单机并发 1、prefetch 1,共享任务身份池。
源码最多 256 KiB,测试点最多 256 个;stdout/stderr 各不超过 file_bytes,
合计不超过 workdir_bytes。增加 Worker 数量不会扩大节点槽位。
以下开发命令均在 `backend/` 运行;迁移账号与平台账号分别配置:
```sh
uv run --locked alembic upgrade head # 私有迁移账号,仅执行受控 schema 操作
uv run --locked kf-accounts create local_user # 换成平台角色;终端读取密码并二次确认
uv run --locked kf-problems /srv/private-import
uv run --locked uvicorn kf_oj.api.app:create_app --factory --host 127.0.0.1 --port 8000 \
--no-proxy-headers --limit-concurrency 32 --backlog 64
uv run --locked python -m kf_oj.infra.messaging.publisher
```
账号和导入目录由管理员准备,浏览器不能传入私有路径。导入格式见
[题目模块](../backend/src/kf_oj/modules/problems/README.md)。题库、题面和提交使用
不可变版本 UUID;更新需发布新版本。临时验收使用合成数据和测试口令,业务数据与
配置放在仓库外。
取消功能还需 API 配置 `KF_OJ_COORDINATOR_URL=http://127.0.0.1:8100` 和
`KF_OJ_COORDINATOR_TOKEN`,后者对应 Coordinator 的 platform_token。
独立生成服务令牌,保存在私有配置中。Coordinator/Worker JSON 为 0600、单链接、
有界文件;Root 持有镜像、配置和二进制。运行前读回节点、服务和任务的
memory/swap/pids/CPU/文件/期限上限。
开发前端在 `frontend/` 运行 `pnpm dev`,默认 127.0.0.1:5173。
平台明确允许相同 Origin,`/api` 使用同源代理。生产入口还需 HTTPS、安全 Cookie、
SPA 回退和 API 反向代理。登录后在题面提交源码,按同一 submission UUID 查看状态;
等待、取消中和系统失败各有独立状态。
### 停止与故障恢复
先向 Worker 发送 SIGTERM,使当前任务取消并清理,再停止 publisher/API/Coordinator,
最后停止工厂和本地依赖。强制死亡后的旧 attempt 必须撤销并取得停止证明。
`cleanup_failed` 保留未确认资源,管理员检查故障来源后再恢复工厂/Worker。
不要递归清空任务目录或强改结果。
broker 暂停期间,API 保留 queued/outbox;恢复后 publisher confirm 与 Coordinator
补偿重新投递。重启 Worker/Coordinator 时保留配置、工件、日志、DB 和 attempt。
旧租约不能覆盖新结果,基础设施重试有次数上限。
容量满时返回 429 或 503。先检查待执行和清理状态,再恢复服务或核对扩容。
`kf-artifacts --limit 100` 只回收达到年龄要求的无引用工件和已标记删除项,
保留仍被题目或提交引用的源码。Root 恢复还需核对 journal、inode 和实际排空。
## 四语言节点的受控升级
`judge.admission.example.json` 是显式 v1/v2 注册表,
`sandbox.managed.runtime.example.json` 使用一槽位共享 Root 配置。
原 v1 示例语义保留。示例中的 `aaaaaaaa…` 是无权限占位摘要,必须换成固定镜像
manifest.json 的实际 SHA-256,并逐文件验证版本、字节和权限。
准入注册表与 Root runtime_images 使用同一完整身份;Root 镜像路径仅放入私有配置。
1. 停止创建提交和消费投递,向普通 Worker 发 SIGTERM,保留 DB、清单、工件和 journal。
2. 确认工厂 stopped/cleanup_complete,读回空任务 cgroup 和 owned 资源。
发现未知资源或未排空时停止升级并保留证据。
3. 用独立账号执行 Backend Alembic upgrade。含新语言数据时旧迁移拒绝降级;
保留这些数据。平台与 Coordinator 使用各自最小权限角色,Worker 无 DB 账号。
4. 安装固定版本 Backend/Coordinator/Worker/Sandbox,同时更新普通服务的同一
`judge_admission`/`admission`。Root 配置单独保管,旧消费者不混用新协议。
5. 设置并读回节点、控制面、工厂和任务的 CPU/memory/swap/pids/文件/期限上限。
至少预留一份 512 MiB 编译任务和独立控制面;Java 运行示例总预算 384 MiB,
Xmx 只是堆上限。并发 1、prefetch 1,使用共享 UID 池。
6. 在断网、无宿主共享挂载、无业务凭据的受限独立 VM 验证四语言、故障和排空,
再恢复服务。生产启用单独安排。
Python `main.py` 仅支持标准库。Java `Main.java` 使用默认包 Main,最多 256 个
有界编译 class。请求不在线安装依赖,不运行 pip/uvx,不接收解释器参数、JAR 或 agents。
题目版本决定语言和预算,旧题目、草稿和报告保留;Rust 暂不支持。
CE、RE、真实超限与 SystemError 的区别见
[判题说明](../judge/README.md#python--java-与明确协议版本)。
Worker 的 `control_timeout_ms` 可设为 1000 到 5000,缺省 2000。
按实际领取、续租和停止耗时选择,并核对租约与恢复流程。
该值不改变学生阶段/作业期限、重试次数、工件下载的固定 2 秒期限或 1 秒连接期限。
回退旧软件前先排空 v2 任务,保留 v2 不可变版本和报告供兼容消费者读取。
恢复时沿用停止与故障流程,保持 outbox 和有限重试。
公开示例不含业务令牌;数据库、AMQP 和服务令牌分别在仓库外生成,Root 不持有它们。
## 验收与 CI
所有学生源码、隔离、超限和故障测试都在无 NIC、无宿主共享挂载、无业务凭据且
整体上限已读回的独立 VM 运行。配置解析、readiness 和存活探测不能替代执行验收。
仓库不自动部署生产。
需要 workflow 时,在 GitHub 私有仓库 `KiriAky107/KF-OnlineJudge` 设置有限并发执行。
Gitea 保存工程提交,不运行 workflow。