BitzOrcas 的生产 Host 由 API、JobHost 和 Gateway 三个独立进程组成。仓库不把任何一种部署拓扑声明成唯一正式方式,而是为三条常见路径提供配套脚本和清单:单机 systemd、单机 docker-compose,以及 Kubernetes。三条路径都要满足同一组不变约束——迁移必须在常驻服务启动前完成、三服务按依赖顺序启停、/health/ready 失败必须阻断流量。
三条路径对照
| 维度 | systemd 单机 | docker-compose 单机 | Kubernetes |
|---|---|---|---|
| 编排单元 | bitzorcas-{api,jobhost,gateway}-<env> 三个 unit | api jobhost gateway 三个 service | Deployment + Service + Job |
| 入口脚本 | scripts/deploy/deploy.sh;零停机切流另有 deploy-blue-green.sh | deploy/docker-compose.app.yml 叠加层 | deploy/kubernetes/*.yaml |
| 打包 | scripts/deploy/pack.* 三件套,单 zip 含三 Host | 同 deploy/container/*.Dockerfile 构建三镜像 | 同 systemd/compose 镜像或 digest |
| 迁移执行 | deploy.sh 内联调用 --init-schema + --init-quartz-schema | 一次性 migrate-schema/migrate-quartz service | migrate-job.yaml 必须成功完成 |
| 配置来源 | /etc/bitzorcas/<env>.env(EnvironmentFile=) | deploy/app.env + compose environment | configmap.yaml + secret.yaml |
| 回滚 | deploy.sh 自动备份;蓝绿用 deploy-rollback.sh 回切上一颜色 | docker compose down 后恢复镜像 tag | kubectl rollout undo |
| 适合规模 | 单实例、资源敏感、不跑 Docker daemon | 单机一体化、环境可复现、客户机一致 | 多副本、自动扩缩、故障域隔离 |
共同的不变约束
无论选择哪条路径,部署流程都必须保证以下顺序,否则迁移与启动会竞争,或健康检查误判就绪:
- 数据库、Redis、RabbitMQ 先就绪。
- API 迁移器执行
--init-schema(自动CREATE DATABASE+ 业务表 + 审计分表 + 幂等种子)。 - JobHost 迁移器执行
--init-quartz-schema(QuartzAdoJobStore持久化表)。 - 启动 JobHost,再启动 API,最后启动 Gateway(Gateway 反向代理 API,依赖 API 健康)。
- 三服务各自的
/health/ready返回 200 后才切流量。
停止顺序相反:Gateway → API → JobHost。
systemd 单机部署
准备
服务器一次性准备:
# ① 安装 .NET 10 Runtime(迁移器和常驻进程都需要,不需 SDK)# Ubuntu 示例;CentOS/RHEL 用 dnfsudo apt-get install -y dotnet-runtime-10.0dotnet --version # 应显示 10.0.x
# ② 创建目录结构sudo mkdir -p /www/{apps,releases,backups,scripts} /etc/bitzorcas
# ③ 放置部署脚本(来自代码仓库 scripts/deploy/)sudo cp scripts/deploy/deploy.sh /www/scripts/deploy.shsudo chmod +x /www/scripts/deploy.sh
# ④ 生成 deployment-id(community.small 的人数计量锚,首次设置后不可更改)sudo mkdir -p /www/apps/<env>/.bitzorcas/licensecat /proc/sys/kernel/random/uuid | tr -d '-' \ | sudo tee /www/apps/<env>/.bitzorcas/license/deployment-id配置环境文件
/etc/bitzorcas/<env>.env 同时被 deploy.sh(load_env_file 解析供迁移器,不能 source 含非法 shell 标识符的键)和三个 systemd unit(EnvironmentFile=)引用,因此三服务共享同一份配置:
sudo cp scripts/deploy/env.example /etc/bitzorcas/<env>.envsudo chmod 600 /etc/bitzorcas/<env>.env # 含数据库密码与 JWT 密钥,必须仅 root 可读sudo nano /etc/bitzorcas/<env>.env必填项见 scripts/deploy/env.example 注释,关键是:
ConnectionStrings__Default:数据库连接串PiiEncryption__SearchHashKey:openssl rand -base64 32,API 与 JobHost 必须同一稳定值Jwt__Secret:openssl rand -base64 48,≥32 字符Licensing__Runtime__PolicyId:community.small.v1(≤30 人阶段)或商业策略Frontend__BaseUrl/Cors__AllowedOrigins__0:与浏览器地址栏完全一致(含协议与端口)Gateway__KnownProxies__0:生产 ingress CIDR,禁止只填 loopback;预览可127.0.0.1/8ReverseProxy__Clusters__<api|signalr|files>__Destinations:集群 id 禁止连字符(systemd 会忽略api-cluster)OpenApi__Enabled/OpenApi__RequireAuthentication/OpenApi__PersistAuthentication:Production 通常关闭文档;共享预览如开启,必须要求产品文档会话,并保持 Scalar 认证持久化为 falseOpenApi__Servers__0__Url:文档面开启时填写公开 Origin,IP 入口必须包含非默认端口
数据库初始化模式(deploy.sh 读取 env;不要把「跳过演示用户」理解成「完全不跑种子」):
| 模式 / 变量 | 默认 | 含义 |
|---|---|---|
BITZORCAS_DEPLOY_DB_MODE | (未设时由下方旧开关推导) | schema-only / platform-seed / full-seed,推荐首选 |
schema-only | — | --init-schema --no-seed:建库/建表/补字段 + 审计分表 + CAP Outbox,完全不跑任何种子(含数据字典)。开发日常发版推荐 |
platform-seed | 推导默认 | 跑平台种子(字典/模块等),SkipSeedIds 跳过演示用户/角色/租户。仍会跑字典等种子 |
full-seed | — | 平台种子 + Demo 用户/租户/Host 账号,需全部 USER__*__PASSWORD(_HASH) |
BITZORCAS_INIT_NO_SEED | 0 | 兼容写法:1 → 等同 schema-only |
BITZORCAS_SKIP_DEMO_SEED | 1 | 兼容写法:未设 DEPLOY_DB_MODE 时,1 → platform-seed,0 → full-seed |
BITZORCAS_SCHEMA_FULL_INIT | 0 | 1 时 Schema 强制全量 CodeFirst;默认按 SQL Server 目录只补缺表/缺列/缺索引 |
USER__ADMIN__PASSWORD 等 | 空 | 仅 full-seed(或 SKIP_DEMO_SEED=0)必填 |
打包
本地打包三 Host 为单个 zip(内含 api/ jobhost/ gateway/ 三个子目录 + manifest.txt):
# 首次部署:强制全量./scripts/deploy/pack.sh <env> <version> --full
# 后续增量:必须先把服务器 /www/apps/<env>/manifest.txt# 下载为 publish 目录中的 manifest-server-<env>-*.txt,再打包(不要 --full)scp <user>@<server>:/www/apps/<env>/manifest.txt \ ../publish/<分支>/manifest-server-<env>-$(date +%m%d%H%M).txt./scripts/deploy/pack.sh <env> <version>
# Windows PowerShell(脚本 UTF-8,启动时强制控制台 UTF-8).\scripts\deploy\pack.ps1 -Env <env> -Version <version>.\scripts\deploy\pack-frontend.ps1 -Environment <env> -Version <version>增量只信任 manifest-server-<env>-*.txt。本地 manifest-baseline-* 仅审计用;没有服务器基线时会安全回退为全量包。产物输出到 <项目上级>/publish/<分支>/bitzorcas-<env>-<version>.zip。
部署
# 上传到服务器scp bitzorcas-<env>-<version>.zip <user>@<server>:/www/releases/
# 默认 platform-seed:跑字典等平台种子,跳过演示用户(不是「仅建表」)ssh <user>@<server> "sudo /www/scripts/deploy.sh /www/releases/bitzorcas-<env>-<version>.zip <env>"
# 开发迭代推荐:仅建表/补字段,完全不跑种子(含字典)ssh <user>@<server> "sudo BITZORCAS_DEPLOY_DB_MODE=schema-only /www/scripts/deploy.sh /www/releases/bitzorcas-<env>-<version>.zip <env>"# 兼容旧写法:# ssh <user>@<server> "sudo BITZORCAS_INIT_NO_SEED=1 /www/scripts/deploy.sh /www/releases/bitzorcas-<env>-<version>.zip <env>"
# 首次演示环境(需 USER__*__PASSWORD):# ssh <user>@<server> "sudo BITZORCAS_DEPLOY_DB_MODE=full-seed /www/scripts/deploy.sh /www/releases/bitzorcas-<env>-<version>.zip <env>"deploy.sh 内部按共同不变约束执行:前置检查 → 停止三服务(Gateway→API→JobHost)→ 端口释放 → 备份 → 解压 → 写入 Gateway appsettings.Deploy.json(下游地址)→ API schema(按 DEPLOY_DB_MODE 决定是否跑种子)→ JobHost --init-quartz-schema → 生成三个 systemd unit → 启动(JobHost→API→Gateway)→ 健康检查(失败打印 journal)→ 失败自动回滚 → 成功后安装服务器 manifest.txt。
Schema 侧:SQL Server 目录探测后,对象齐全则跳过 CodeFirst;仅部分表/列/索引缺失时只对受影响模型 InitTables;BITZORCAS_SCHEMA_FULL_INIT=1 或探测失败则全量。探测只校验对象存在性,不比较列类型/长度。
判断成功
| 端点 | 期望 |
|---|---|
systemctl status bitzorcas-api-<env> | active (running) |
curl localhost:8080/health/ready | 200 |
curl localhost:8081/health/ready | 200(JobHost 仅暴露 health,无业务 API) |
curl localhost:8082/health/ready | 200(Gateway,对外入口) |
curl localhost:8080/health/license | community.small 显示 LIC.COMMUNITY_SMALL + 当前人数 |
/openapi/v1.json 与 /scalar/v1 | Production 为 404;受外层保护的预览按配置返回 200 |
日志:journalctl -u 'bitzorcas-*-<env>' -f
1Panel 机器需要零停机切 Gateway 时,不要继续用 deploy.sh 原地覆盖。先按 1Panel OpenResty 单机蓝绿 把 location 改成 named upstream,再:
# ① 先接线 named upstream,再发空闲颜色;回切不碰数据库。sudo /www/scripts/setup-1panel-openresty.sh <env>sudo BITZORCAS_DEPLOY_DB_MODE=schema-only \ /www/scripts/deploy-blue-green.sh /www/releases/bitzorcas-<env>-<version>.zip <env>sudo /www/scripts/deploy-rollback.sh <env>回滚
deploy.sh 在每次部署前做增量备份到 /www/backups/<env>/,健康检查失败时自动回滚(恢复备份 → 重跑迁移 → 重启)。手动回滚:
# 先恢复上一版文件并校正两套数据库结构,再恢复对外流量。sudo systemctl stop bitzorcas-gateway-<env> bitzorcas-api-<env> bitzorcas-jobhost-<env>sudo tar -xzf /www/backups/<env>/backup_<timestamp>.tar.gz -C /www/apps/<env>sudo dotnet /www/apps/<env>/api/BitzOrcas.Api.dll --init-schemasudo dotnet /www/apps/<env>/jobhost/BitzOrcas.JobHost.dll --init-quartz-schemasudo systemctl start bitzorcas-jobhost-<env> bitzorcas-api-<env> bitzorcas-gateway-<env>docker-compose 单机部署
适合不想要裸进程管理、希望环境可复现的单机场景。仓库根 docker-compose.yml 只编排基础设施(SQL Server / RabbitMQ / Redis / AgileConfig / Qdrant),应用栈由 deploy/docker-compose.app.yml 叠加层补充。
# ① 准备配置cp .env.example .env # 基础设施凭据cp deploy/app.env.example deploy/app.env # 应用配置(License/DataProtection/CORS)
# ② 构建三镜像docker build -f deploy/container/Dockerfile -t bitzorcas-api:local .docker build -f deploy/container/JobHost.Dockerfile -t bitzorcas-jobhost:local .docker build -f deploy/container/Gateway.Dockerfile -t bitzorcas-gateway:local .
# ③ 启动(迁移器 service 自动先跑,成功后 api/jobhost/gateway 依次起)docker compose -f docker-compose.yml -f deploy/docker-compose.app.yml up -ddepends_on + condition: service_completed_successfully 保证迁移器先于常驻服务完成。第一阶段(community.small)使用 ASPNETCORE_ENVIRONMENT=Development,因为 Production 环境受 RuntimeConfigurationGuard 强制完整生产依赖(见 production-configuration)。
Kubernetes 部署
多副本、自动扩缩、故障域隔离的生产场景。清单在 deploy/kubernetes/:
# 填充 configmap.yaml 的所有 replace-with-* 占位符# 创建 bitzorcas-api-runtime Secret + 独立的 license envelope Secretkubectl apply -f deploy/kubernetes/namespace.yamlkubectl apply -f deploy/kubernetes/configmap.yaml
# 迁移 Job 必须成功完成,才能 apply Deploymentskubectl apply -f deploy/kubernetes/migrate-job.yamlkubectl wait --for=condition=complete job/bitzorcas-schema-migrator -n bitzorcas
kubectl apply -f deploy/kubernetes/api.yamlkubectl apply -f deploy/kubernetes/jobhost.yamlkubectl apply -f deploy/kubernetes/gateway.yamlK8s 路径的完整契约见 deploy/kubernetes/README.md:运行时副本不在启动时跑 schema、readOnlyRootFilesystem、deployment-id 必须跨副本一致、PII HMAC 轮换不支持滚动发布。
License 衔接
三条路径都使用同一套从 community.small(≤30 活跃自然人用户,免签名 License)切换到商业 License(完整 Platform 能力)的步骤:
- 部署
BitzOrcas.LicenseSigner(独立内网服务,持有 KMS/HSM 私钥,仅 Bearer 认证)。 - 在环境配置中追加可信公钥
Licensing__Runtime__TrustedPublicKeys__<key-id>。 - 通过 LicenseSigner 控制面四眼审批签发,产出
runtime-license.json(绑定当前deployment-id)。 - 切换
Licensing__Runtime__PolicyId到商业策略,重启 API 与 JobHost。
切换后 /health/license 从 LIC.COMMUNITY_SMALL 变为 Valid,30 人上限解除。详见商业运行许可证。
常见故障
| 现象 | 首先检查 |
|---|---|
| deploy.sh 迁移失败中止 | /etc/bitzorcas/<env>.env 的连接串;迁移输出日志 |
| API 启动后立即退出 | journalctl -u bitzorcas-api-<env>;常见为 Redis/RabbitMQ 不可达或 PII HMAC 缺失 |
| JobHost 启动失败 | Quartz schema 是否已迁移(--init-quartz-schema) |
| Gateway 返回 502 | API 是否 ready;systemctl status bitzorcas-api-<env> 与 API 日志 |
/health/license 返回 503 | License 策略或人数超限;community.small 上限 30 活跃用户 |
| 健康检查超时触发自动回滚 | deploy.sh 输出的失败服务;通常是 DB/Redis/MQ 依赖不可达 |
| compose 迁移器卡住 | migrate-schema service 日志;数据库连接串或 SA 密码 |
| Gateway Production 启动拒绝 | Gateway__KnownProxies 是否显式指定 ingress CIDR(禁止只填 loopback) |
| Scalar 调试请求丢失公开端口 | 边缘代理是否使用 $http_host 并发送 X-Forwarded-Port;OpenAPI Server 是否正确 |