diff --git a/content/docs/deploy/air-gapped.zh-Hans.mdx b/content/docs/deploy/air-gapped.zh-Hans.mdx new file mode 100644 index 00000000..50dd4294 --- /dev/null +++ b/content/docs/deploy/air-gapped.zh-Hans.mdx @@ -0,0 +1,105 @@ +--- +title: 物理隔离部署 +seoTitle: "物理隔离部署:许可证模式和设置" +description: 物理隔离是一种许可证模式,而不是防火墙设置 —— 断网部署必须声明的两项设置,以及会在启动时被拒绝的组合。 +editions: [enterprise] +translation: + source_sha: d0fbf234ac1fc9be7bcdb652e29a259e133103a63533267bf3f065bdee48ce9b + guide_rev: 1 + mode: auto +--- + +## 物理隔离是一种许可证模式 [#air-gap-is-a-license-mode] + +拔掉网线并不能得到一个物理隔离的部署。普通的 ObjectOS 许可证是**在线**校验的: +运行时在控制平面上用你的许可证密钥换取一个短期授权,并定期重新检查。切断这条 +路径,许可证并不会"回落到离线"—— 它根本永远无法生效。 + +**物理隔离许可证是另一种许可证**,由我们有意离线签发。它在本地依据镜像中内嵌的 +公钥进行校验,并以**零网络流量**挂载你的授权。物理隔离运行是一项企业版能力 —— +参见[许可证与定价](/docs/editions/license) —— 你需要的许可证是申请来的,而不是 +配置出来的。 + +因此,一个断网部署要同时声明**两**件事: + +- 一个**物理隔离许可证**,以及 +- **没有控制平面**:云端姿态设置被显式设为 `off`(它的别名 `none`、`local` 和 + `disabled` 含义相同)。 + +## 不设置并不等于"不连云" [#leaving-the-setting-unset-is-not-no-cloud] + +这是本页上代价最高的错误,所以它有自己的一节: + +> **未设置的云端姿态会解析为公共控制平面。** + +未设置从来不是中立的。它选中的是联网行为 —— 这就是为什么一个"什么都没做"的部署 +最终会挂载一个市场代理,并暴露一个设备绑定路由,而这个站点的全部前提就是不与 +任何人通信。声明一个姿态会被接受;**不做决定则不行。**请显式写上 `off`。 + +## 受支持的组合 [#the-supported-combinations] + +这些组合在加载配置时、在任何与许可证相关的事情发生**之前**就会被检查。不受支持 +的组合不是警告,也不是降级:运行时会打印一条同时点名*两项*设置的致命错误,然后 +退出。 + +| 许可证 | 云端姿态 | 结果 | +|---|---|---| +| 物理隔离许可证 | `off` | **运行,完整授权,零网络流量。**这就是断网场景的答案。 | +| 无许可证 | `off` | 以 Community 行为运行。注意,多组织姿态仍然需要许可证。 | +| 普通(在线)许可证 | 可达的控制平面,且已完成设备绑定 | 正常的联网部署。 | +| 普通(在线)许可证 | `off` | **在启动时被拒绝。** | +| 普通(在线)许可证 | 未设置 | 被视为公共控制平面 —— 见上文。能否运行取决于那个控制平面是否真的可达且已绑定。 | + +### 为什么会拒绝 [#why-the-refusal-exists] + +在线许可证通过一次交换来校验,这次交换必须出示一个在部署绑定控制平面时签发的 +运行时令牌。没有控制平面就没有绑定,没有绑定令牌就不可能存在,于是交换永远不会 +发生 —— 这次启动不会,以后的启动也不会。宽限期也帮不上忙:宽限期从上一次*成功* +的校验开始计算,而第一次成功永远不会出现。 + +如果不加以强制,这种配置会在之后才失败,而且归咎于错误的东西:在多组织姿态下, +它最终会抱怨部署"需要许可证"—— 而许可证恰恰是唯一从未出错的东西。所以运行时会 +更早地拒绝,并说明是哪两项设置相互矛盾。由于拒绝发生在任何校验尝试之前,一次被 +拒绝的启动**不会发出任何数据包**。 + +## 托管的多组织形态也需要物理隔离许可证 [#the-hosted-multi-organization-shape-is-air-gap-licensed-too] + +如果你运行的是**组合**形态 —— 一个已发布的应用作为本部署的应用启动,多个组织 +在隔离墙后共享一个数据库 —— 那么物理隔离许可证在那里同样不是可选的。该模式直接 +拒绝市场和云连接界面,所以在线许可证所需的绑定在它上面永远不可能发生。它要求 +物理隔离许可证和 `off`,两者都在启动时强制执行,并且它在部署包中有自己的环境 +模板。不要改写单环境模板来达到它;复制组合形态的那个模板,这样它所决定的每一个 +值都会随之而来。 + +## 把镜像带进网络内部 [#getting-the-image-inside-the-network] + +ObjectOS 运行时镜像位于断网站点无法访问的私有镜像仓库中,所以镜像要像你的其他 +产物一样跨越边界。 + +在联网一侧完成校验,趁你还能访问镜像仓库和工具:针对你即将送入内网的确切摘要, +检查**签名**、**SBOM** 和**构建溯源**(部署包的 README 中有相关命令)。然后让 +**摘要**完成剩下的工作 —— 它是按内容寻址的,所以确认网络内部的副本具有相同的 +摘要,就证明了它与你校验过的是同一批字节。这也是部署固定摘要而不是标签的原因: +在边界之内,没有任何东西可以用来重新解析标签。 + +升级的方式相同:在外部校验新的摘要,把它传入内部,修改固定的摘要,然后重启。 +保留上一个摘要 —— 在物理隔离的网络中,它可能是你唯一能回滚到的副本。 + +## 哪些仍然必须可达 [#what-still-has-to-be-reachable] + +物理隔离的意思是 *ObjectOS 本身*不需要任何流量 —— 没有许可证校验,没有市场, +没有更新检查。它并不意味着部署没有网络。在你的边界之内,它仍然需要: + +- 它所配置的数据库(集群时还有 Redis); +- 你的入口或负载均衡器,用于入站流量; +- **你**配置它去通信的任何东西:身份提供商、SMTP、对象存储、webhook 目标、AI + 提供商端点。如果你的用户通过 OIDC 登录,那么这个身份提供商必须能从该网络访问, + 否则就改用本地账户。 + +许可证本身带有到期时间,在每次启动时本地检查 —— 没有回连,所以要把续期规划成 +一次交付,而不是指望运行时自己去获取。 + +## 下一步 [#next] + +- [Docker → 企业版额外提供](/docs/deploy/docker#enterprise-adds) —— 完整的校验、许可证、扩缩容和升级。 +- [升级](/docs/operate/upgrade)和[备份](/docs/operate/backup) —— 上述步骤所概括的操作流程。 diff --git a/content/docs/deploy/air-gapped.zh-Hant.mdx b/content/docs/deploy/air-gapped.zh-Hant.mdx new file mode 100644 index 00000000..1eefc3a2 --- /dev/null +++ b/content/docs/deploy/air-gapped.zh-Hant.mdx @@ -0,0 +1,106 @@ +--- +# @generated zh-Hant from zh-Hans -- do not edit. Edit the English source, then regenerate: pnpm --filter @objectos/docs gen:zh-hant +title: 物理隔離部署 +seoTitle: "物理隔離部署:許可證模式和設定" +description: 物理隔離是一種許可證模式,而不是防火牆設定 —— 斷網部署必須宣告的兩項設定,以及會在啟動時被拒絕的組合。 +editions: [enterprise] +translation: + source_sha: d0fbf234ac1fc9be7bcdb652e29a259e133103a63533267bf3f065bdee48ce9b + guide_rev: 1 + mode: auto +--- + +## 物理隔離是一種許可證模式 [#air-gap-is-a-license-mode] + +拔掉網線並不能得到一個物理隔離的部署。普通的 ObjectOS 許可證是**線上**校驗的: +執行時在控制平面上用你的許可證金鑰換取一個短期授權,並定期重新檢查。切斷這條 +路徑,許可證並不會"回落到離線"—— 它根本永遠無法生效。 + +**物理隔離許可證是另一種許可證**,由我們有意離線簽發。它在本地依據映象中內嵌的 +公鑰進行校驗,並以**零網路流量**掛載你的授權。物理隔離執行是一項企業版能力 —— +參見[許可證與定價](/docs/editions/license) —— 你需要的許可證是申請來的,而不是 +配置出來的。 + +因此,一個斷網部署要同時宣告**兩**件事: + +- 一個**物理隔離許可證**,以及 +- **沒有控制平面**:雲端姿態設定被顯式設為 `off`(它的別名 `none`、`local` 和 + `disabled` 含義相同)。 + +## 不設定並不等於"不連雲" [#leaving-the-setting-unset-is-not-no-cloud] + +這是本頁上代價最高的錯誤,所以它有自己的一節: + +> **未設定的雲端姿態會解析為公共控制平面。** + +未設定從來不是中立的。它選中的是聯網行為 —— 這就是為什麼一個"什麼都沒做"的部署 +最終會掛載一個市場代理,並暴露一個裝置繫結路由,而這個站點的全部前提就是不與 +任何人通訊。宣告一個姿態會被接受;**不做決定則不行。**請顯式寫上 `off`。 + +## 受支援的組合 [#the-supported-combinations] + +這些組合在載入配置時、在任何與許可證相關的事情發生**之前**就會被檢查。不受支援 +的組合不是警告,也不是降級:執行時會列印一條同時點名*兩項*設定的致命錯誤,然後 +退出。 + +| 許可證 | 雲端姿態 | 結果 | +|---|---|---| +| 物理隔離許可證 | `off` | **執行,完整授權,零網路流量。**這就是斷網場景的答案。 | +| 無許可證 | `off` | 以 Community 行為執行。注意,多組織姿態仍然需要許可證。 | +| 普通(線上)許可證 | 可達的控制平面,且已完成裝置繫結 | 正常的聯網部署。 | +| 普通(線上)許可證 | `off` | **在啟動時被拒絕。** | +| 普通(線上)許可證 | 未設定 | 被視為公共控制平面 —— 見上文。能否執行取決於那個控制平面是否真的可達且已繫結。 | + +### 為什麼會拒絕 [#why-the-refusal-exists] + +線上許可證通過一次交換來校驗,這次交換必須出示一個在部署繫結控制平面時簽發的 +執行時令牌。沒有控制平面就沒有繫結,沒有繫結令牌就不可能存在,於是交換永遠不會 +發生 —— 這次啟動不會,以後的啟動也不會。寬限期也幫不上忙:寬限期從上一次*成功* +的校驗開始計算,而第一次成功永遠不會出現。 + +如果不加以強制,這種配置會在之後才失敗,而且歸咎於錯誤的東西:在多組織姿態下, +它最終會抱怨部署"需要許可證"—— 而許可證恰恰是唯一從未出錯的東西。所以執行時會 +更早地拒絕,並說明是哪兩項設定相互矛盾。由於拒絕發生在任何校驗嘗試之前,一次被 +拒絕的啟動**不會發出任何資料包**。 + +## 託管的多組織形態也需要物理隔離許可證 [#the-hosted-multi-organization-shape-is-air-gap-licensed-too] + +如果你執行的是**組合**形態 —— 一個已釋出的應用作為本部署的應用啟動,多個組織 +在隔離牆後共享一個數據庫 —— 那麼物理隔離許可證在那裡同樣不是可選的。該模式直接 +拒絕市場和雲連線介面,所以線上許可證所需的繫結在它上面永遠不可能發生。它要求 +物理隔離許可證和 `off`,兩者都在啟動時強制執行,並且它在部署包中有自己的環境 +模板。不要改寫單環境模板來達到它;複製組合形態的那個模板,這樣它所決定的每一個 +值都會隨之而來。 + +## 把映象帶進網路內部 [#getting-the-image-inside-the-network] + +ObjectOS 執行時映象位於斷網站點無法訪問的私有映象倉庫中,所以映象要像你的其他 +產物一樣跨越邊界。 + +在聯網一側完成校驗,趁你還能訪問映象倉庫和工具:針對你即將送入內網的確切摘要, +檢查**簽名**、**SBOM** 和**構建溯源**(部署包的 README 中有相關命令)。然後讓 +**摘要**完成剩下的工作 —— 它是按內容定址的,所以確認網路內部的副本具有相同的 +摘要,就證明了它與你校驗過的是同一批位元組。這也是部署固定摘要而不是標籤的原因: +在邊界之內,沒有任何東西可以用來重新解析標籤。 + +升級的方式相同:在外部校驗新的摘要,把它傳入內部,修改固定的摘要,然後重啟。 +保留上一個摘要 —— 在物理隔離的網路中,它可能是你唯一能回滾到的副本。 + +## 哪些仍然必須可達 [#what-still-has-to-be-reachable] + +物理隔離的意思是 *ObjectOS 本身*不需要任何流量 —— 沒有許可證校驗,沒有市場, +沒有更新檢查。它並不意味著部署沒有網路。在你的邊界之內,它仍然需要: + +- 它所配置的資料庫(叢集時還有 Redis); +- 你的入口或負載均衡器,用於入站流量; +- **你**配置它去通訊的任何東西:身份提供商、SMTP、物件儲存、webhook 目標、AI + 提供商端點。如果你的使用者通過 OIDC 登入,那麼這個身份提供商必須能從該網路訪問, + 否則就改用本地賬戶。 + +許可證本身帶有到期時間,在每次啟動時本地檢查 —— 沒有回連,所以要把續期規劃成 +一次交付,而不是指望執行時自己去獲取。 + +## 下一步 [#next] + +- [Docker → 企業版額外提供](/docs/deploy/docker#enterprise-adds) —— 完整的校驗、許可證、擴縮容和升級。 +- [升級](/docs/operate/upgrade)和[備份](/docs/operate/backup) —— 上述步驟所概括的操作流程。 diff --git a/content/docs/deploy/docker.zh-Hans.mdx b/content/docs/deploy/docker.zh-Hans.mdx new file mode 100644 index 00000000..75561116 --- /dev/null +++ b/content/docs/deploy/docker.zh-Hans.mdx @@ -0,0 +1,380 @@ +--- +title: Docker +seoTitle: "Docker:运行 ObjectOS Community 和 Enterprise" +description: 用 Docker 运行 ObjectOS —— 固定镜像、设置三个必需的变量、用 Compose 加上 PostgreSQL、接好探针和代理、升级与回滚。企业版镜像还增加了私有镜像仓库、校验和许可证。 +editions: [community, enterprise] +translation: + source_sha: 7fb88e85e29666d6c9ec80215a289bfa2bbe75dc1a35ab842ddd53f33bf4ea72 + guide_rev: 1 + mode: auto +--- + +Docker 是在你自己的服务器上运行 ObjectOS 的标准方式。两个自管理版本的步骤相同: +一个被精确固定的镜像,配置在运行时注入,数据库放在容器之外。本页以开放的 +**Community** 镜像 `ghcr.io/objectstack-ai/objectstack` 逐步讲解;末尾的 +[企业版额外提供](#enterprise-adds)一节说明在需许可证的镜像上有什么不同。 + +## 固定镜像 [#pin-the-image] + +Community 镜像随每个 ObjectStack 版本发布。它的标签与镜像内 ObjectStack CLI 的 +版本一致:版本 `X.Y.Z` 以 `X.Y.Z` 推送,并把 `latest`、`X.Y` 和 `X` 移到它上面。 +滚动标签会随每次发布而移动,所以对 `latest` 做一次例行的 `docker compose pull`, +就可能在没有任何部署事件的情况下替换你正在运行的版本。 + +确切的标签固定的是版本,而不是字节。维护者可以重新构建一个已经发布的版本,例如 +为了纳入 `node:22-slim` 基础镜像中的安全补丁,而重新构建会把同一个 `X.Y.Z` 标签 +再次推送到一个新镜像上。镜像内的 ObjectStack CLI 仍是同一个版本;基础镜像,以及 +它按版本范围安装的数据库驱动,则会重新解析。 +[发布工作流](https://github.com/objectstack-ai/objectstack/blob/main/.github/workflows/docker-publish.yml) +声明了这种重新构建。只有**摘要**永远指向同样的字节。 + +**在生产环境中运行确切的版本**,与构建你的应用的 CLI 版本相同;凡是你需要每台 +主机、每次重启都运行同样字节的地方,还要固定它的摘要。从镜像仓库读取摘要: + +```bash +docker buildx imagetools inspect ghcr.io/objectstack-ai/objectstack:X.Y.Z +``` + +固定第一行 `Digest:`,也就是 `Name:` 下面那一行。它指向整个镜像,包括两种架构; +`Manifests:` 下列出的摘要各自只指向一个平台。按这个摘要引用镜像,并在旁边保留 +版本号,因为摘要本身并不说明它是哪个版本: + +```bash +# ObjectStack X.Y.Z +ghcr.io/objectstack-ai/objectstack@sha256:<64 hex characters> +``` + +把上一个版本及其摘要记下来:那就是回滚。镜像发布了 `linux/amd64` 和 `linux/arm64` 两种架构,以非 root +用户运行,监听 **8080** 端口,并自带健康检查。它包含 PostgreSQL(`pg`)和 MySQL +(`mysql2`)驱动。 + +## 你要设置的三个值 [#the-three-values-you-set] + +其他一切都有可用的默认值。这三个没有: + +| 变量 | 缺少它会怎样 | +|---|---| +| `OS_DATABASE_URL` | 数据会落到运行时主目录下的一个 SQLite 文件中 —— 在一台持久的主机上没问题,但会随容器的文件系统一起丢失。生产环境使用 `postgres://…`。 | +| `OS_AUTH_SECRET` | `/api/v1/auth/*` 下的登录端点会被**跳过**,服务器在没有认证的情况下运行。 | +| `OS_SECRET_KEY` | 运行时会为存储的密钥自行生成一个加密密钥,并把它保存在自己的文件系统上。在容器中,这个密钥会随容器一起消失,用它加密的每个密钥都会变得无法读取。 | + +两个密钥只生成一次,并保存在你的密钥管理器中: + +```bash +OS_AUTH_SECRET=$(openssl rand -hex 32) +OS_SECRET_KEY=$(openssl rand -hex 32) +``` + +**绝不要把任何一个密钥烘焙进镜像。**在运行时从编排器的密钥存储中传入。把 +`OS_SECRET_KEY` 与数据库一起备份:恢复时没有带上密钥的数据库无法解密它自己的 +密钥 —— 参见[备份](/docs/operate/backup)。完整列表见 +[环境变量](/docs/reference/environment-variables)。 + +## 运行它 [#run-it] + +镜像不带任何应用。把 `os build` 编译出的 `objectstack.json` 交给它,有三种方式。 + +**挂载产物。**完全不用构建镜像 —— 镜像会在 `/srv/app/objectstack.json` 查找应用: + +```bash +docker run -p 8080:8080 \ + -v "$PWD/dist/objectstack.json:/srv/app/objectstack.json:ro" \ + -e OS_DATABASE_URL="postgres://objectos:secret@db-host:5432/objectos" \ + -e OS_AUTH_SECRET \ + -e OS_SECRET_KEY \ + ghcr.io/objectstack-ai/objectstack:X.Y.Z +``` + +**按 URL 指定产物。**`OS_ARTIFACT_URL` 指向一个已发布的产物,所以一个固定的镜像 +加上一个变量就是一个运行中的应用,而升级应用就是修改这个变量再重启: + +```bash +docker run -p 8080:8080 \ + -e OS_ARTIFACT_URL="https://releases.example.com/crm-2.2.2.json#sha256=<64 hex characters>" \ + -e OS_DATABASE_URL -e OS_AUTH_SECRET -e OS_SECRET_KEY \ + ghcr.io/objectstack-ai/objectstack:X.Y.Z +``` + +可选的 `#sha256=` 片段会固定产物的字节,运行时会据此约束启动: + +| 情况 | 运行时的行为 | +|---|---| +| 没有固定值 | 不做校验直接启动。如果无法获取产物,启动会**失败**,以便编排器重试 —— 没有可以回落的缓存副本。 | +| 有固定值,字节匹配 | 启动,并保留已校验的副本。 | +| 有固定值,字节不同 | **拒绝启动**,并点名期望的摘要和实际的摘要。 | +| 有固定值,主机不可达 | 只有当缓存副本仍与固定值匹配时才从它启动,并以一条警告说明这一点。 | + +`file:///…` 也可以用于挂载的文件。把产物发布为不可变的、以版本命名的对象,并只给 +你的发布流水线写入权限 —— 正是这一点让回滚应用与升级应用成为同一种操作。 + +**把产物复制进你自己的镜像。**每个版本一个自包含的镜像。项目脚手架 +(`npm create objectstack`)会为你写一个这种形态的 Dockerfile: + +```dockerfile title="Dockerfile" +FROM node:22-slim AS build +WORKDIR /app +COPY package.json pnpm-lock.yaml* pnpm-workspace.yaml* package-lock.json* ./ +RUN if [ -f pnpm-lock.yaml ]; then corepack pnpm@10 install --frozen-lockfile; \ + else npm ci; fi +COPY . . +RUN npx os build # → dist/objectstack.json + +FROM ghcr.io/objectstack-ai/objectstack:X.Y.Z +COPY --from=build --chown=node:node /app/dist/objectstack.json /srv/app/objectstack.json +``` + +## 用 Compose 加上 PostgreSQL [#compose-with-postgresql] + +一个单主机的生产技术栈:上面的镜像,旁边是它的数据库。 + +```yaml title="docker-compose.yml" +services: + app: + build: . + ports: + - "8080:8080" + environment: + OS_DATABASE_URL: postgres://objectos:${POSTGRES_PASSWORD}@db:5432/objectos + OS_AUTH_SECRET: ${OS_AUTH_SECRET} + OS_SECRET_KEY: ${OS_SECRET_KEY} + depends_on: + db: + condition: service_healthy + restart: unless-stopped + + db: + image: postgres:17 + environment: + POSTGRES_USER: objectos + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: objectos + volumes: + - pgdata:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U objectos -d objectos"] + interval: 5s + timeout: 3s + retries: 10 + restart: unless-stopped + +volumes: + pgdata: +``` + +```bash +# .env beside docker-compose.yml — never committed +POSTGRES_PASSWORD=… +OS_AUTH_SECRET=… +OS_SECRET_KEY=… + +docker compose up -d +curl -fsS http://localhost:8080/api/v1/health +``` + +当你迁移到托管的 PostgreSQL 时,把 `OS_DATABASE_URL` 指向它,并移除 `db` 服务。 +模式会在运行时启动时更新到最新:安全的变更会自行运行,而破坏性的变更会**拒绝 +启动**并点名每一项变更,直到你用 `os migrate apply --allow-destructive` 有意识地 +应用它([CLI](/docs/reference/cli#database-migrations--os-migrate))。 + +## 健康检查 [#health-checks] + +两个端点,它们不能互换: + +| 端点 | 回答什么 | 接到 | +|---|---|---| +| `/api/v1/health` | 进程已启动并在提供 HTTP 服务。它从不访问数据库。 | 存活探针 | +| `/api/v1/ready` | 运行时已启动**且其数据驱动有响应**。只要有一个驱动没有响应,就返回 `503` 并点名失败的驱动。 | 就绪探针,以及你的负载均衡器 | + +如果接反了,后果很糟。把存活探针接到 `/api/v1/ready`,会在数据库故障期间重启每个 +副本 —— 而重启解决不了这个问题 —— 还会把配置故障变成一个掩盖它的重启循环。应该 +做出反应的是就绪探针:数据库不可用时,它把副本从轮转中移出,数据库恢复后再把它 +放回来,不需要重启。应用所绑定的数据源也必须在启动时连上,否则启动会直接失败: +一个无法访问的数据库就是一次失败的部署,而不是一个先启动、然后在每个请求上都 +报错的容器。 + +## 反向代理和 TLS [#reverse-proxy-and-tls] + +在运行时前面终止 TLS —— Caddy、nginx、Traefik 或你的云负载均衡器 —— 并让三个值 +与公网地址保持一致: + +```bash +OS_AUTH_URL=https://app.example.com # sign-in callbacks and cookie origin +OS_TRUSTED_ORIGINS=https://app.example.com # sign-in callback and CSRF allow-list +OS_PORT=8080 # the proxy's upstream port +``` + +```text title="Caddyfile" +app.example.com { + reverse_proxy localhost:8080 +} +``` + +端口或源对不上是自托管时的经典故障:应用在运行,但登录会被弹回,浏览器会阻止 +API 调用。只要 Console 和 API 共享同一个公网源,CORS 就不需要任何配置。从**另一个** +源提供的前端要显式列在 `OS_CORS_ORIGIN` 中 —— 在开启凭据时,`*` 会在启动时被 +拒绝。只有在确认 TLS 正常后才开启 HSTS;其余内容见 +[生产就绪](/docs/operate/production)。 + +## 首次启动:第一个管理员 [#first-boot-the-first-administrator] + +在一个全新的数据库上,打开部署的地址并**注册 —— 第一个账户会成为管理员。**在首次 +部署后立即完成这一步,再分享地址。生产服务器不会预置任何默认凭据:你可能在 +`os dev` 中见过的 `admin@objectos.ai` 账户只存在于开发数据库中。 + +如果要改为在配置中指定管理员,就把 `OS_PLATFORM_OWNER_EMAIL` 设为一个地址或一个 +逗号分隔的列表;列出的每个账户在其邮箱地址验证后即拥有该身份。多组织姿态要求设置 +它 —— 参见[环境变量](/docs/reference/environment-variables#multi-organization-deployments)。 + +一个预置了人员记录、却没有登录账户的应用会关上这扇门:第一个账户的规则计算的是 +人员,而不是登录。启动日志会说明这一点 —— 参见 +[故障排查](/docs/operate/troubleshooting#nobody-can-sign-in)。 + +## 多节点 [#multi-node] + +ObjectOS Community 运行**一个副本**。协调 —— 锁、队列、调度 —— 是进程内的,所以 +第二个副本无法共享它。在没有许可证的情况下声明多个副本(`OS_CLUSTER_REPLICAS` +大于 1)会**拒绝启动**,而不是用按进程的锁运行它们。集群与高可用是企业版能力 —— +参见下文的[多节点](#multi-node-on-enterprise)。 + +## 升级与回滚 [#upgrade-and-rollback] + +1. 阅读你所跨越的每个 ObjectStack 版本的发布说明,以及它们要求运维人员执行的 + [特定版本步骤](/docs/operate/upgrade#version-specific-steps)。 +2. **备份数据库。**模式迁移只能向前:回滚镜像并不会回滚模式。 +3. 把镜像改为新的确切版本,如果你固定了摘要,就改为它的摘要,然后重启。模式会在 + 启动时、运行时报告就绪之前更新到最新。 +4. 确认 `/api/v1/ready` 返回 200。 +5. 要回滚,就恢复上一个标签或摘要并重复第 3 步 —— 只要你要离开的版本没有做不兼容的 + 模式变更,这就是有效的。 + +应用单独进行版本管理:修改 `OS_ARTIFACT_URL` 或你挂载的产物,就能在不动镜像的 +情况下升级它。 + +## 企业版额外提供 [#enterprise-adds] + +需许可证的企业版镜像以同样的方式运行,并增加了私有镜像仓库、供应链校验、许可证和 +多节点;它的发布说明会点名要固定的摘要。你的版本会附带一个**部署包**,其中包含一个 Compose 技术栈、 +每种部署形态一个环境模板,以及一份带有适用于你的确切版本的命令的 README。本节是 +该部署包所配置内容的决策地图。 + +### 你会得到什么 [#what-you-are-given] + +你的许可证附带私有运行时镜像的**镜像仓库凭据**。登录需要一个具有包**读取**权限的 +令牌;没有公开的企业版镜像,也不能从源码构建:镜像由我们的发布流水线从不分发的 +源码产出,所以 `docker build` 对它来说不是一条存在的路径。 + +**受支持的架构:仅 `linux/amd64`。**这是一项声明,而不是遗漏:构建会拒绝为我们未 +声明的架构产出镜像。Apple Silicon 和 Graviton 主机会在模拟下运行它,这用来看一看 +没问题,用于生产则是错的。 + +### 按摘要固定镜像 [#pin-the-image-by-digest] + +你在生产环境中运行的镜像引用是一个**摘要**,而不是标签: + +``` +OS_EE_IMAGE=@sha256:<64 hex characters> +``` + +标签是人类可读的名称,可以被重新指向不同的字节。摘要是按内容寻址的:同一个摘要 +永远是同一个镜像,在每台主机上、永远如此。使用标签的话,事后就无法证明出问题时 +实际运行的是什么。你的发布说明会给出摘要;原样粘贴即可。也要保留**上一个**摘要。 + +### 镜像可以告诉你它是什么 [#the-image-can-tell-you-what-it-is] + +镜像带有两个不同的版本号,不应混为一谈:ObjectOS 的**产品版本**,以及其中内嵌的 +**ObjectStack 运行时的兼容范围**,后者按自己的节奏变化。两者 —— 连同构建镜像所用 +的源码修订版本 —— 都记录在镜像本身中,作为 OCI 标签和一份机器可读的发布清单。你 +永远不必来问我们你拿到的是哪个构建。 + +这份清单也是附在支持请求中最有用的东西:它足以重现你的确切镜像。 + +### 在生产之前校验镜像 [#verify-the-image-before-production] + +镜像是**经过签名的**(无密钥签名,通过我们发布流水线的 OIDC 身份),并附带一份 +**SBOM** 和**构建溯源**。在生产上线之前校验一次 —— 三项检查回答三个不同的问题: + +| 检查 | 它回答的问题 | +|---|---| +| 签名 | 这个摘要是由 ObjectOS 发布流水线产出的,而不是别人? | +| SBOM | 这个镜像里有什么? | +| 溯源 | 这个镜像是如何、从哪些源码构建出来的? | + +部署包的 README 中有校验命令,以及要核对的身份和签发者,与你的版本相匹配。 + +### 许可证 [#license] + +**许可证是正常路径的一部分。**企业版镜像在没有许可证时的行为,取决于你要求它成为 +什么: + +- **单组织,无许可证** —— 部署以 ObjectOS Community 运行:开放的 MCP 服务器, + 自带你自己的 AI。什么都不会变砖。 +- **单组织,有许可证** —— 产品内 AI 会在你自己的基础设施上解锁:AI Builder 和 + "问你的数据"。 +- **多组织(隔离墙式租户姿态),无许可证** —— 运行时会**在启动时退出**,并点名 + 许可证。组织之间的隔离是一项需许可证的能力,一个要求隔离的部署绝不能假装拥有 + 隔离来提供服务。 + +同一条线上还有第二个约束,而它正是让人意外的那个:**许可证有模式,而模式与你的 +云端姿态耦合。**普通许可证针对控制平面在线校验;物理隔离许可证在本地校验,完全 +不需要网络。把普通许可证与"没有控制平面"搭配不是一种降级状态,而是一种不可能的 +状态 —— 它会**在启动时被拒绝**,并点名两项设置,而不是之后才以一条关于许可证的 +误导性抱怨失败。[物理隔离](/docs/deploy/air-gapped)给出了受支持组合的完整矩阵。 + +### 隔离墙式部署必须再声明两项决策 [#a-walled-deployment-must-declare-two-more-decisions] + +在多组织姿态下,有两项设置没有安全的默认值,所以运行时会**拒绝启动,直到你做出 +决定** —— 声明任何一个可用的值都会被接受;不做决定则不行: + +| 决策 | 为什么不能有默认值 | +|---|---| +| **新注册的用户加入什么** | 在成员资格*就是*租户边界的地方,它只能通过显式的行为授予 —— 邀请、管理员、SSO 开通。自动加入会把一个陌生人放进一个现有的组织。 | +| **挂载哪些 AI Agent** | "ask" Agent 只读;"build" Agent **编写元数据** —— 而元数据的作用域是部署,而不是组织。在共享数据库上,一个客户的一次 build 对话就会改写其他所有客户运行所依赖的模式。 | + +单组织部署不会看到这两项检查。 + +### 出厂技术栈 [#the-shipped-stack] + +部署包中的 Compose 技术栈按部署必须遵循的顺序编排,值得了解这个顺序,因为任何 +其他编排器都必须重现它: + +1. 数据库变为健康状态。 +2. 一个**一次性迁移**把模式运行到完成 —— 在任何应用副本启动之前,所以并发的 + 副本永远不会相互对抗地执行模式变更。 +3. 应用副本启动。镜像自带健康检查。 +4. 反向代理只有在某个副本真正有响应时才开始转发 —— 而不仅仅是被创建出来。 + +在生产环境中,把部署指向你**托管的** PostgreSQL(如果做集群,还有 Redis),并移除 +捆绑的数据库服务。把你的域名交给代理,即可获得自动 HTTPS。运行时容器以非 root +用户运行,并把运行时状态保存在一个声明的卷上。 + +### 企业版上的多节点 [#multi-node-on-enterprise] + +扩展到一个副本以上,会改变哪些是必需的: + +- 开启**集群驱动**(以 Redis 为后端),并且 +- **共享的加密密钥成为必需。**没有集群驱动时,运行时会自行生成并持久保存它自己的 + 密钥。跨副本时这些密钥会悄悄地互不相同 —— 所以**运行时会拒绝启动**,而不是让 + 它们分叉。生成一个密钥,并给每个副本相同的值。 +- **认证密钥也必须在每个副本上完全相同。**修改它会使所有现有会话失效,并让签名 + 密钥成为孤儿;环境模板说明了轮换时附带的那一次性清理步骤。 +- `OS_CLUSTER_REPLICAS` 设置捆绑的技术栈启动多少个应用副本。 + +### 按摘要升级 [#upgrade-by-digest] + +上面的升级步骤同样适用,只是用摘要代替标签: + +1. 从发布说明中取得**新的摘要**,并在上线之前阅读镜像的发布清单:确认产品主版本 + 线是你所在的那一条,并检查内嵌运行时的兼容范围是否移动了。运行时主版本的移动 + 意味着底层有破坏性变更 —— 先阅读那个版本的发布说明。 +2. 备份数据库。 +3. 修改镜像摘要并重启技术栈。迁移会在新副本接收流量之前运行到完成。 +4. 确认就绪端点返回 200,且副本报告为健康。 +5. 要回滚,就恢复上一个摘要并重复第 3 步 —— 只要你要离开的版本没有做不兼容的 + 模式变更,这就是有效的(发布说明会说明何时做了这样的变更)。 + +## 下一步 [#next] + +- [Kubernetes](/docs/deploy/kubernetes) —— 同一个镜像,运行在编排器上。 +- [生产就绪](/docs/operate/production) —— 上线前的检查清单。 +- [可观测性](/docs/operate/observability) —— 日志、指标、审计。 +- [物理隔离](/docs/deploy/air-gapped) —— 面向断网站点的企业版许可证模式。 diff --git a/content/docs/deploy/docker.zh-Hant.mdx b/content/docs/deploy/docker.zh-Hant.mdx new file mode 100644 index 00000000..a4a8e10f --- /dev/null +++ b/content/docs/deploy/docker.zh-Hant.mdx @@ -0,0 +1,381 @@ +--- +# @generated zh-Hant from zh-Hans -- do not edit. Edit the English source, then regenerate: pnpm --filter @objectos/docs gen:zh-hant +title: Docker +seoTitle: "Docker:執行 ObjectOS Community 和 Enterprise" +description: 用 Docker 執行 ObjectOS —— 固定映象、設定三個必需的變數、用 Compose 加上 PostgreSQL、接好探針和代理、升級與回滾。企業版映象還增加了私有映象倉庫、校驗和許可證。 +editions: [community, enterprise] +translation: + source_sha: 7fb88e85e29666d6c9ec80215a289bfa2bbe75dc1a35ab842ddd53f33bf4ea72 + guide_rev: 1 + mode: auto +--- + +Docker 是在你自己的伺服器上執行 ObjectOS 的標準方式。兩個自管理版本的步驟相同: +一個被精確固定的映象,配置在執行時注入,資料庫放在容器之外。本頁以開放的 +**Community** 映象 `ghcr.io/objectstack-ai/objectstack` 逐步講解;末尾的 +[企業版額外提供](#enterprise-adds)一節說明在需許可證的映象上有什麼不同。 + +## 固定映象 [#pin-the-image] + +Community 映象隨每個 ObjectStack 版本釋出。它的標籤與映象內 ObjectStack CLI 的 +版本一致:版本 `X.Y.Z` 以 `X.Y.Z` 推送,並把 `latest`、`X.Y` 和 `X` 移到它上面。 +滾動標籤會隨每次釋出而移動,所以對 `latest` 做一次例行的 `docker compose pull`, +就可能在沒有任何部署事件的情況下替換你正在執行的版本。 + +確切的標籤固定的是版本,而不是位元組。維護者可以重新構建一個已經發布的版本,例如 +為了納入 `node:22-slim` 基礎映象中的安全補丁,而重新構建會把同一個 `X.Y.Z` 標籤 +再次推送到一個新映象上。映象內的 ObjectStack CLI 仍是同一個版本;基礎映象,以及 +它按版本範圍安裝的資料庫驅動,則會重新解析。 +[釋出工作流](https://github.com/objectstack-ai/objectstack/blob/main/.github/workflows/docker-publish.yml) +聲明瞭這種重新構建。只有**摘要**永遠指向同樣的位元組。 + +**在生產環境中執行確切的版本**,與構建你的應用的 CLI 版本相同;凡是你需要每臺 +主機、每次重啟都運行同樣位元組的地方,還要固定它的摘要。從映象倉庫讀取摘要: + +```bash +docker buildx imagetools inspect ghcr.io/objectstack-ai/objectstack:X.Y.Z +``` + +固定第一行 `Digest:`,也就是 `Name:` 下面那一行。它指向整個映象,包括兩種架構; +`Manifests:` 下列出的摘要各自只指向一個平臺。按這個摘要引用映象,並在旁邊保留 +版本號,因為摘要本身並不說明它是哪個版本: + +```bash +# ObjectStack X.Y.Z +ghcr.io/objectstack-ai/objectstack@sha256:<64 hex characters> +``` + +把上一個版本及其摘要記下來:那就是回滾。映象釋出了 `linux/amd64` 和 `linux/arm64` 兩種架構,以非 root +使用者執行,監聽 **8080** 埠,並自帶健康檢查。它包含 PostgreSQL(`pg`)和 MySQL +(`mysql2`)驅動。 + +## 你要設定的三個值 [#the-three-values-you-set] + +其他一切都有可用的預設值。這三個沒有: + +| 變數 | 缺少它會怎樣 | +|---|---| +| `OS_DATABASE_URL` | 資料會落到執行時主目錄下的一個 SQLite 檔案中 —— 在一臺持久的主機上沒問題,但會隨容器的檔案系統一起丟失。生產環境使用 `postgres://…`。 | +| `OS_AUTH_SECRET` | `/api/v1/auth/*` 下的登入端點會被**跳過**,伺服器在沒有認證的情況下執行。 | +| `OS_SECRET_KEY` | 執行時會為儲存的金鑰自行生成一個加密金鑰,並把它儲存在自己的檔案系統上。在容器中,這個金鑰會隨容器一起消失,用它加密的每個金鑰都會變得無法讀取。 | + +兩個金鑰只生成一次,並儲存在你的金鑰管理器中: + +```bash +OS_AUTH_SECRET=$(openssl rand -hex 32) +OS_SECRET_KEY=$(openssl rand -hex 32) +``` + +**絕不要把任何一個金鑰烘焙進映象。**在執行時從編排器的金鑰儲存中傳入。把 +`OS_SECRET_KEY` 與資料庫一起備份:恢復時沒有帶上金鑰的資料庫無法解密它自己的 +金鑰 —— 參見[備份](/docs/operate/backup)。完整列表見 +[環境變數](/docs/reference/environment-variables)。 + +## 執行它 [#run-it] + +映象不帶任何應用。把 `os build` 編譯出的 `objectstack.json` 交給它,有三種方式。 + +**掛載產物。**完全不用構建映象 —— 映象會在 `/srv/app/objectstack.json` 查詢應用: + +```bash +docker run -p 8080:8080 \ + -v "$PWD/dist/objectstack.json:/srv/app/objectstack.json:ro" \ + -e OS_DATABASE_URL="postgres://objectos:secret@db-host:5432/objectos" \ + -e OS_AUTH_SECRET \ + -e OS_SECRET_KEY \ + ghcr.io/objectstack-ai/objectstack:X.Y.Z +``` + +**按 URL 指定產物。**`OS_ARTIFACT_URL` 指向一個已釋出的產物,所以一個固定的映象 +加上一個變數就是一個執行中的應用,而升級應用就是修改這個變數再重啟: + +```bash +docker run -p 8080:8080 \ + -e OS_ARTIFACT_URL="https://releases.example.com/crm-2.2.2.json#sha256=<64 hex characters>" \ + -e OS_DATABASE_URL -e OS_AUTH_SECRET -e OS_SECRET_KEY \ + ghcr.io/objectstack-ai/objectstack:X.Y.Z +``` + +可選的 `#sha256=` 片段會固定產物的位元組,執行時會據此約束啟動: + +| 情況 | 執行時的行為 | +|---|---| +| 沒有固定值 | 不做校驗直接啟動。如果無法獲取產物,啟動會**失敗**,以便編排器重試 —— 沒有可以回落的快取副本。 | +| 有固定值,位元組匹配 | 啟動,並保留已校驗的副本。 | +| 有固定值,位元組不同 | **拒絕啟動**,並點名期望的摘要和實際的摘要。 | +| 有固定值,主機不可達 | 只有當快取副本仍與固定值匹配時才從它啟動,並以一條警告說明這一點。 | + +`file:///…` 也可以用於掛載的檔案。把產物釋出為不可變的、以版本命名的物件,並只給 +你的釋出流水線寫入許可權 —— 正是這一點讓回滾應用與升級應用成為同一種操作。 + +**把產物複製進你自己的映象。**每個版本一個自包含的映象。專案腳手架 +(`npm create objectstack`)會為你寫一個這種形態的 Dockerfile: + +```dockerfile title="Dockerfile" +FROM node:22-slim AS build +WORKDIR /app +COPY package.json pnpm-lock.yaml* pnpm-workspace.yaml* package-lock.json* ./ +RUN if [ -f pnpm-lock.yaml ]; then corepack pnpm@10 install --frozen-lockfile; \ + else npm ci; fi +COPY . . +RUN npx os build # → dist/objectstack.json + +FROM ghcr.io/objectstack-ai/objectstack:X.Y.Z +COPY --from=build --chown=node:node /app/dist/objectstack.json /srv/app/objectstack.json +``` + +## 用 Compose 加上 PostgreSQL [#compose-with-postgresql] + +一個單主機的生產技術棧:上面的映象,旁邊是它的資料庫。 + +```yaml title="docker-compose.yml" +services: + app: + build: . + ports: + - "8080:8080" + environment: + OS_DATABASE_URL: postgres://objectos:${POSTGRES_PASSWORD}@db:5432/objectos + OS_AUTH_SECRET: ${OS_AUTH_SECRET} + OS_SECRET_KEY: ${OS_SECRET_KEY} + depends_on: + db: + condition: service_healthy + restart: unless-stopped + + db: + image: postgres:17 + environment: + POSTGRES_USER: objectos + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: objectos + volumes: + - pgdata:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U objectos -d objectos"] + interval: 5s + timeout: 3s + retries: 10 + restart: unless-stopped + +volumes: + pgdata: +``` + +```bash +# .env beside docker-compose.yml — never committed +POSTGRES_PASSWORD=… +OS_AUTH_SECRET=… +OS_SECRET_KEY=… + +docker compose up -d +curl -fsS http://localhost:8080/api/v1/health +``` + +當你遷移到託管的 PostgreSQL 時,把 `OS_DATABASE_URL` 指向它,並移除 `db` 服務。 +模式會在執行時啟動時更新到最新:安全的變更會自行執行,而破壞性的變更會**拒絕 +啟動**並點名每一項變更,直到你用 `os migrate apply --allow-destructive` 有意識地 +應用它([CLI](/docs/reference/cli#database-migrations--os-migrate))。 + +## 健康檢查 [#health-checks] + +兩個端點,它們不能互換: + +| 端點 | 回答什麼 | 接到 | +|---|---|---| +| `/api/v1/health` | 程序已啟動並在提供 HTTP 服務。它從不訪問資料庫。 | 存活探針 | +| `/api/v1/ready` | 執行時已啟動**且其資料驅動有響應**。只要有一個驅動沒有響應,就返回 `503` 並點名失敗的驅動。 | 就緒探針,以及你的負載均衡器 | + +如果接反了,後果很糟。把存活探針接到 `/api/v1/ready`,會在資料庫故障期間重啟每個 +副本 —— 而重啟解決不了這個問題 —— 還會把配置故障變成一個掩蓋它的重啟迴圈。應該 +做出反應的是就緒探針:資料庫不可用時,它把副本從輪轉中移出,資料庫恢復後再把它 +放回來,不需要重啟。應用所繫結的資料來源也必須在啟動時連上,否則啟動會直接失敗: +一個無法訪問的資料庫就是一次失敗的部署,而不是一個先啟動、然後在每個請求上都 +報錯的容器。 + +## 反向代理和 TLS [#reverse-proxy-and-tls] + +在執行時前面終止 TLS —— Caddy、nginx、Traefik 或你的雲負載均衡器 —— 並讓三個值 +與公網地址保持一致: + +```bash +OS_AUTH_URL=https://app.example.com # sign-in callbacks and cookie origin +OS_TRUSTED_ORIGINS=https://app.example.com # sign-in callback and CSRF allow-list +OS_PORT=8080 # the proxy's upstream port +``` + +```text title="Caddyfile" +app.example.com { + reverse_proxy localhost:8080 +} +``` + +埠或源對不上是自託管時的經典故障:應用在執行,但登入會被彈回,瀏覽器會阻止 +API 呼叫。只要 Console 和 API 共享同一個公網源,CORS 就不需要任何配置。從**另一個** +源提供的前端要顯式列在 `OS_CORS_ORIGIN` 中 —— 在開啟憑據時,`*` 會在啟動時被 +拒絕。只有在確認 TLS 正常後才開啟 HSTS;其餘內容見 +[生產就緒](/docs/operate/production)。 + +## 首次啟動:第一個管理員 [#first-boot-the-first-administrator] + +在一個全新的資料庫上,開啟部署的地址並**註冊 —— 第一個賬戶會成為管理員。**在首次 +部署後立即完成這一步,再分享地址。生產伺服器不會預置任何預設憑據:你可能在 +`os dev` 中見過的 `admin@objectos.ai` 賬戶只存在於開發資料庫中。 + +如果要改為在配置中指定管理員,就把 `OS_PLATFORM_OWNER_EMAIL` 設為一個地址或一個 +逗號分隔的列表;列出的每個賬戶在其郵箱地址驗證後即擁有該身份。多組織姿態要求設定 +它 —— 參見[環境變數](/docs/reference/environment-variables#multi-organization-deployments)。 + +一個預置了人員記錄、卻沒有登入賬戶的應用會關上這扇門:第一個賬戶的規則計算的是 +人員,而不是登入。啟動日誌會說明這一點 —— 參見 +[故障排查](/docs/operate/troubleshooting#nobody-can-sign-in)。 + +## 多節點 [#multi-node] + +ObjectOS Community 執行**一個副本**。協調 —— 鎖、佇列、排程 —— 是程序內的,所以 +第二個副本無法共享它。在沒有許可證的情況下宣告多個副本(`OS_CLUSTER_REPLICAS` +大於 1)會**拒絕啟動**,而不是用按程序的鎖執行它們。叢集與高可用是企業版能力 —— +參見下文的[多節點](#multi-node-on-enterprise)。 + +## 升級與回滾 [#upgrade-and-rollback] + +1. 閱讀你所跨越的每個 ObjectStack 版本的釋出說明,以及它們要求運維人員執行的 + [特定版本步驟](/docs/operate/upgrade#version-specific-steps)。 +2. **備份資料庫。**模式遷移只能向前:回滾映象並不會回滾模式。 +3. 把映象改為新的確切版本,如果你固定了摘要,就改為它的摘要,然後重啟。模式會在 + 啟動時、執行時報告就緒之前更新到最新。 +4. 確認 `/api/v1/ready` 返回 200。 +5. 要回滾,就恢復上一個標籤或摘要並重復第 3 步 —— 只要你要離開的版本沒有做不相容的 + 模式變更,這就是有效的。 + +應用單獨進行版本管理:修改 `OS_ARTIFACT_URL` 或你掛載的產物,就能在不動映象的 +情況下升級它。 + +## 企業版額外提供 [#enterprise-adds] + +需許可證的企業版映象以同樣的方式執行,並增加了私有映象倉庫、供應鏈校驗、許可證和 +多節點;它的釋出說明會點名要固定的摘要。你的版本會附帶一個**部署包**,其中包含一個 Compose 技術棧、 +每種部署形態一個環境模板,以及一份帶有適用於你的確切版本的命令的 README。本節是 +該部署包所配置內容的決策地圖。 + +### 你會得到什麼 [#what-you-are-given] + +你的許可證附帶私有執行時映象的**映象倉庫憑據**。登入需要一個具有包**讀取**許可權的 +令牌;沒有公開的企業版映象,也不能從原始碼構建:映象由我們的釋出流水線從不分發的 +原始碼產出,所以 `docker build` 對它來說不是一條存在的路徑。 + +**受支援的架構:僅 `linux/amd64`。**這是一項宣告,而不是遺漏:構建會拒絕為我們未 +宣告的架構產出映象。Apple Silicon 和 Graviton 主機會在模擬下執行它,這用來看一看 +沒問題,用於生產則是錯的。 + +### 按摘要固定映象 [#pin-the-image-by-digest] + +你在生產環境中執行的映象引用是一個**摘要**,而不是標籤: + +``` +OS_EE_IMAGE=@sha256:<64 hex characters> +``` + +標籤是人類可讀的名稱,可以被重新指向不同的位元組。摘要是按內容定址的:同一個摘要 +永遠是同一個映象,在每臺主機上、永遠如此。使用標籤的話,事後就無法證明出問題時 +實際執行的是什麼。你的釋出說明會給出摘要;原樣貼上即可。也要保留**上一個**摘要。 + +### 映象可以告訴你它是什麼 [#the-image-can-tell-you-what-it-is] + +映象帶有兩個不同的版本號,不應混為一談:ObjectOS 的**產品版本**,以及其中內嵌的 +**ObjectStack 執行時的兼容範圍**,後者按自己的節奏變化。兩者 —— 連同構建映象所用 +的原始碼修訂版本 —— 都記錄在映象本身中,作為 OCI 標籤和一份機器可讀的釋出清單。你 +永遠不必來問我們你拿到的是哪個構建。 + +這份清單也是附在支援請求中最有用的東西:它足以重現你的確切映象。 + +### 在生產之前校驗映象 [#verify-the-image-before-production] + +映象是**經過簽名的**(無金鑰簽名,通過我們釋出流水線的 OIDC 身份),並附帶一份 +**SBOM** 和**構建溯源**。在生產上線之前校驗一次 —— 三項檢查回答三個不同的問題: + +| 檢查 | 它回答的問題 | +|---|---| +| 簽名 | 這個摘要是由 ObjectOS 釋出流水線產出的,而不是別人? | +| SBOM | 這個映象裡有什麼? | +| 溯源 | 這個映象是如何、從哪些原始碼構建出來的? | + +部署包的 README 中有校驗命令,以及要核對的身份和簽發者,與你的版本相匹配。 + +### 許可證 [#license] + +**許可證是正常路徑的一部分。**企業版映象在沒有許可證時的行為,取決於你要求它成為 +什麼: + +- **單組織,無許可證** —— 部署以 ObjectOS Community 執行:開放的 MCP 伺服器, + 自帶你自己的 AI。什麼都不會變磚。 +- **單組織,有許可證** —— 產品內 AI 會在你自己的基礎設施上解鎖:AI Builder 和 + "問你的資料"。 +- **多組織(隔離牆式租戶姿態),無許可證** —— 執行時會**在啟動時退出**,並點名 + 許可證。組織之間的隔離是一項需許可證的能力,一個要求隔離的部署絕不能假裝擁有 + 隔離來提供服務。 + +同一條線上還有第二個約束,而它正是讓人意外的那個:**許可證有模式,而模式與你的 +雲端姿態耦合。**普通許可證針對控制平面線上校驗;物理隔離許可證在本地校驗,完全 +不需要網路。把普通許可證與"沒有控制平面"搭配不是一種降級狀態,而是一種不可能的 +狀態 —— 它會**在啟動時被拒絕**,並點名兩項設定,而不是之後才以一條關於許可證的 +誤導性抱怨失敗。[物理隔離](/docs/deploy/air-gapped)給出了受支援組合的完整矩陣。 + +### 隔離牆式部署必須再宣告兩項決策 [#a-walled-deployment-must-declare-two-more-decisions] + +在多組織姿態下,有兩項設定沒有安全的預設值,所以執行時會**拒絕啟動,直到你做出 +決定** —— 宣告任何一個可用的值都會被接受;不做決定則不行: + +| 決策 | 為什麼不能有預設值 | +|---|---| +| **新註冊的使用者加入什麼** | 在成員資格*就是*租戶邊界的地方,它只能通過顯式的行為授予 —— 邀請、管理員、SSO 開通。自動加入會把一個陌生人放進一個現有的組織。 | +| **掛載哪些 AI Agent** | "ask" Agent 只讀;"build" Agent **編寫後設資料** —— 而後設資料的作用域是部署,而不是組織。在共享資料庫上,一個客戶的一次 build 對話就會改寫其他所有客戶執行所依賴的模式。 | + +單組織部署不會看到這兩項檢查。 + +### 出廠技術棧 [#the-shipped-stack] + +部署包中的 Compose 技術棧按部署必須遵循的順序編排,值得了解這個順序,因為任何 +其他編排器都必須重現它: + +1. 資料庫變為健康狀態。 +2. 一個**一次性遷移**把模式執行到完成 —— 在任何應用副本啟動之前,所以併發的 + 副本永遠不會相互對抗地執行模式變更。 +3. 應用副本啟動。映象自帶健康檢查。 +4. 反向代理只有在某個副本真正有響應時才開始轉發 —— 而不僅僅是被創建出來。 + +在生產環境中,把部署指向你**託管的** PostgreSQL(如果做叢集,還有 Redis),並移除 +捆綁的資料庫服務。把你的域名交給代理,即可獲得自動 HTTPS。執行時容器以非 root +使用者執行,並把執行時狀態儲存在一個宣告的捲上。 + +### 企業版上的多節點 [#multi-node-on-enterprise] + +擴充套件到一個副本以上,會改變哪些是必需的: + +- 開啟**叢集驅動**(以 Redis 為後端),並且 +- **共享的加密金鑰成為必需。**沒有叢集驅動時,執行時會自行生成並持久儲存它自己的 + 金鑰。跨副本時這些金鑰會悄悄地互不相同 —— 所以**執行時會拒絕啟動**,而不是讓 + 它們分叉。生成一個金鑰,並給每個副本相同的值。 +- **認證金鑰也必須在每個副本上完全相同。**修改它會使所有現有會話失效,並讓簽名 + 金鑰成為孤兒;環境模板說明了輪換時附帶的那一次性清理步驟。 +- `OS_CLUSTER_REPLICAS` 設定捆綁的技術棧啟動多少個應用副本。 + +### 按摘要升級 [#upgrade-by-digest] + +上面的升級步驟同樣適用,只是用摘要代替標籤: + +1. 從釋出說明中取得**新的摘要**,並在上線之前閱讀映象的釋出清單:確認產品主版本 + 線是你所在的那一條,並檢查內嵌執行時的兼容範圍是否移動了。執行時主版本的移動 + 意味著底層有破壞性變更 —— 先閱讀那個版本的釋出說明。 +2. 備份資料庫。 +3. 修改映象摘要並重啟技術棧。遷移會在新副本接收流量之前執行到完成。 +4. 確認就緒端點返回 200,且副本報告為健康。 +5. 要回滾,就恢復上一個摘要並重復第 3 步 —— 只要你要離開的版本沒有做不相容的 + 模式變更,這就是有效的(釋出說明會說明何時做了這樣的變更)。 + +## 下一步 [#next] + +- [Kubernetes](/docs/deploy/kubernetes) —— 同一個映象,執行在編排器上。 +- [生產就緒](/docs/operate/production) —— 上線前的檢查清單。 +- [可觀測性](/docs/operate/observability) —— 日誌、指標、審計。 +- [物理隔離](/docs/deploy/air-gapped) —— 面向斷網站點的企業版許可證模式。 diff --git a/content/docs/deploy/index.zh-Hans.mdx b/content/docs/deploy/index.zh-Hans.mdx new file mode 100644 index 00000000..a1faac65 --- /dev/null +++ b/content/docs/deploy/index.zh-Hans.mdx @@ -0,0 +1,101 @@ +--- +title: 部署 +seoTitle: "部署:自管理 ObjectOS,社区版和企业版" +description: 在你自己的基础设施上运行 ObjectOS —— 开放的社区版镜像或需许可证的企业版镜像,同一个运行时、同一份配置契约,以及许可证额外带来什么。 +editions: [community, enterprise] +translation: + source_sha: 8e3a6c8cf20d9cbca8dbf4e4162996b6398eecf09e17b90912fd9914ec86e3f6 + guide_rev: 1 + mode: auto +--- + +ObjectOS 有两个自管理版本,它们的部署方式相同。**ObjectOS Community** 是开源 +的运行时镜像;**ObjectOS Enterprise** 是需许可证的镜像,可以作为 Business +Self-Managed 运行在单节点上,也可以作为完整的企业版部署。两者运行同一个运行时, +读取同一份配置:一个数据库、一个认证密钥和一个加密密钥。不同之处在于镜像从哪里 +来、应用如何进入,以及许可证解锁什么。本节的每一页都同时为两者编写一次,并以 +**企业版额外提供**一节结尾,说明需许可证的镜像能多做什么。 + +## 两个镜像 [#the-two-images] + +| | **Community** | **Enterprise** | +|---|---|---| +| 镜像 | `ghcr.io/objectstack-ai/objectstack`,公开 | 需许可证的企业版镜像,来自私有镜像仓库;你的许可证附带镜像仓库凭据 | +| 固定方式 | 确切的版本标签(`X.Y.Z`),与镜像内的 ObjectStack CLI 一致;在字节不能变化的地方,还要固定它的**摘要**([原因](/docs/deploy/docker#pin-the-image)) | 你的发布说明中点名的镜像**摘要** | +| 架构 | `linux/amd64` 和 `linux/arm64` | 仅 `linux/amd64` | +| 应用如何到达 | 你用 `os build` 编译出的 `objectstack.json`:挂载进去、复制进你自己的镜像,或由 `OS_ARTIFACT_URL` 指定。空的运行时则改为从 Console 市场安装应用。 | 三种运行形态之一:应用打包在镜像中、一个已发布的产物,或者一个与镜像的企业插件组合在一起的已发布产物([见下文](#how-the-app-gets-in)) | +| 必须设置的变量 | `OS_DATABASE_URL`、`OS_AUTH_SECRET`、`OS_SECRET_KEY`(镜像已经设置了 `OS_PORT=8080`) | 同上,另加 `OS_LICENSE_KEY` 和一个经过考虑的 `OS_CLOUD_URL` | +| 可复制的配置 | 这些页面上的示例 | 随每个版本发布的**部署包** | +| 许可证解锁什么 | —— | 产品内 AI(AI Builder、"问你的数据")、AI 治理、集群与高可用、一个部署上的多个组织、SCIM、物理隔离运行 | + +[版本对比](/docs/editions/compare)给出了完整的矩阵。在 Community 上构建的应用 +就是普通的 ObjectStack 元数据,不做任何修改即可在 Enterprise 上运行。 + +## 每个部署都要决定三件事 [#every-deployment-decides-three-things] + +| 决策 | 含义 | +|---|---| +| **运行哪些字节** | 固定一个确切的镜像。像 `latest` 这样的滚动标签可能在你不知情时被重新指向,确切的版本标签在该版本被重新构建时也会如此;只有摘要不会。保留上一个版本:那就是回滚。 | +| **配置放在哪里** | 放在镜像和应用之外。数据库 URL、密钥和部署的身份都是在运行时注入的环境变量,绝不烘焙进镜像。[环境变量](/docs/reference/environment-variables)就是契约。 | +| **哪个数据库** | 生产环境运行在 PostgreSQL 上。没有 `OS_DATABASE_URL` 时,运行时会在其主目录下写一个 SQLite 文件,这适合一台持久的主机,但在容器的临时文件系统上会丢失数据。 | + +## 应用如何进入 [#how-the-app-gets-in] + +Community 镜像**不带任何应用**。你把 `os build` 编译出的产物 +`dist/objectstack.json` 交给它,有三种方式: + +| 方式 | 做法 | 何时使用 | +|---|---|---| +| **挂载** | 把文件挂载到 `/srv/app/objectstack.json`,镜像默认在那里查找 | 你完全不想构建镜像 | +| **复制进你自己的镜像** | 一个 `FROM` 官方镜像的两阶段 Dockerfile | 你每个版本发布一个自包含的镜像 | +| **按 URL 指定** | `OS_ARTIFACT_URL`,可选带一个 `#sha256=` 完整性固定值 | 你希望升级应用就是一次环境变更加一次重启 | + +[Docker](/docs/deploy/docker#run-it) 展示了全部三种方式。完全没有产物时,运行时 +会启动一个空内核,应用从 Console 市场安装。 + +## 指南 [#guides] + +- [Docker](/docs/deploy/docker) —— 运行镜像、配置它、把它放到代理后面、升级它。 +- [Kubernetes](/docs/deploy/kubernetes) —— 在编排器上运行同一个镜像,以及需要保持的属性。 +- [物理隔离](/docs/deploy/air-gapped) —— 仅限企业版:物理隔离是一种**许可证模式**,以及会在启动时被拒绝的组合。 + +没有容器运行时的话,同一个产物可以在装有 Node.js 22 的主机上通过 `os start` +运行 —— 参见[在本地运行 ObjectOS Community](/docs/start/community)。 + +## 企业版额外提供 [#enterprise-adds] + +### 另外两个决策:许可证和云端姿态 [#two-more-decisions-license-and-cloud-posture] + +| 决策 | 含义 | +|---|---| +| **哪种许可证** | 许可证是正常路径的一部分,而不是附加项。有些部署形态没有许可证会*拒绝启动*。 | +| **哪种云端姿态** | 这个运行时是否与控制平面通信。它**与许可证模式耦合**,不受支持的组合会在启动时被拒绝,而不是悄悄降级。 | + +实践中这两者是同一个决策,而选错的后果是启动失败,而不是缓慢的泄漏。 +[物理隔离](/docs/deploy/air-gapped)完整列出了受支持的组合 —— 即使你的站点是联网 +的也请读一读,因为正是那一页解释了这些组合*为什么*是现在这样。 + +### 部署包 [#the-deploy-bundle] + +你的版本会附带一个**部署包**:一个 Compose 技术栈、每种受支持形态的环境模板, +以及一份 README,其中有适用于你的版本的确切命令 —— 包括镜像仓库引用、摘要和供应 +链校验步骤。 + +这些页面承载的是**决策** —— 为什么用摘要、哪种形态用哪种许可证模式、多节点使 +什么成为必需、运行时会拒绝什么。部署包承载的是**你要复制的值**。当两者看起来 +不一致时,以随你的镜像发布的部署包为准:它与它所配置的运行时一同版本化。 + +### 三种运行形态 [#three-run-shapes] + +企业版镜像可以用三种方式运行应用,而这个选择会影响你能用什么 —— 它不仅仅是 +打包方式的问题。 + +| 形态 | 应用来自 | 何时使用 | +|---|---|---| +| **配置编写** | 镜像自带的应用 —— 镜像中编写的元数据,加上安装进它的任何内容 | 单组织部署;Compose 技术栈配置的默认形态,也是不需要任何配置的形态。 | +| **产物固定** | 一个按 URL 指定的已发布应用产物,在任何部署配置运行之前解析 | 你运行一个已发布的应用,并希望升级应用就是一次环境变更加一次重启,无需重建镜像。注意,在这条路径上部署的企业插件**不会**加载。 | +| **组合** | 一个与本部署的企业插件*组合*在一起的已发布应用产物 | 多个组织共享一个数据库。这是托管的单应用 SaaS 形态 —— 并且**仅限物理隔离许可证**,在启动时强制执行。 | + +每种形态在部署包中都有自己的环境模板。复制适用于你的形态的那一个,而不是改写 +另一个:这些模板的不同之处,正是决定姿态的那些值,而它们恰恰是运行时在提供任何 +服务之前会检查的值。 diff --git a/content/docs/deploy/index.zh-Hant.mdx b/content/docs/deploy/index.zh-Hant.mdx new file mode 100644 index 00000000..960c5f55 --- /dev/null +++ b/content/docs/deploy/index.zh-Hant.mdx @@ -0,0 +1,102 @@ +--- +# @generated zh-Hant from zh-Hans -- do not edit. Edit the English source, then regenerate: pnpm --filter @objectos/docs gen:zh-hant +title: 部署 +seoTitle: "部署:自管理 ObjectOS,社群版和企業版" +description: 在你自己的基礎設施上執行 ObjectOS —— 開放的社群版映象或需許可證的企業版映象,同一個執行時、同一份配置契約,以及許可證額外帶來什麼。 +editions: [community, enterprise] +translation: + source_sha: 8e3a6c8cf20d9cbca8dbf4e4162996b6398eecf09e17b90912fd9914ec86e3f6 + guide_rev: 1 + mode: auto +--- + +ObjectOS 有兩個自管理版本,它們的部署方式相同。**ObjectOS Community** 是開源 +的執行時映象;**ObjectOS Enterprise** 是需許可證的映象,可以作為 Business +Self-Managed 執行在單節點上,也可以作為完整的企業版部署。兩者運行同一個執行時, +讀取同一份配置:一個數據庫、一個認證金鑰和一個加密金鑰。不同之處在於映象從哪裡 +來、應用如何進入,以及許可證解鎖什麼。本節的每一頁都同時為兩者編寫一次,並以 +**企業版額外提供**一節結尾,說明需許可證的映象能多做什麼。 + +## 兩個映象 [#the-two-images] + +| | **Community** | **Enterprise** | +|---|---|---| +| 映象 | `ghcr.io/objectstack-ai/objectstack`,公開 | 需許可證的企業版映象,來自私有映象倉庫;你的許可證附帶映象倉庫憑據 | +| 固定方式 | 確切的版本標籤(`X.Y.Z`),與映象內的 ObjectStack CLI 一致;在位元組不能變化的地方,還要固定它的**摘要**([原因](/docs/deploy/docker#pin-the-image)) | 你的釋出說明中點名的映象**摘要** | +| 架構 | `linux/amd64` 和 `linux/arm64` | 僅 `linux/amd64` | +| 應用如何到達 | 你用 `os build` 編譯出的 `objectstack.json`:掛載進去、複製進你自己的映象,或由 `OS_ARTIFACT_URL` 指定。空的執行時則改為從 Console 市場安裝應用。 | 三種執行形態之一:應用打包在映象中、一個已釋出的產物,或者一個與映象的企業外掛組合在一起的已釋出產物([見下文](#how-the-app-gets-in)) | +| 必須設定的變數 | `OS_DATABASE_URL`、`OS_AUTH_SECRET`、`OS_SECRET_KEY`(映象已經設定了 `OS_PORT=8080`) | 同上,另加 `OS_LICENSE_KEY` 和一個經過考慮的 `OS_CLOUD_URL` | +| 可複製的配置 | 這些頁面上的示例 | 隨每個版本釋出的**部署包** | +| 許可證解鎖什麼 | —— | 產品內 AI(AI Builder、"問你的資料")、AI 治理、叢集與高可用、一個部署上的多個組織、SCIM、物理隔離執行 | + +[版本對比](/docs/editions/compare)給出了完整的矩陣。在 Community 上構建的應用 +就是普通的 ObjectStack 後設資料,不做任何修改即可在 Enterprise 上執行。 + +## 每個部署都要決定三件事 [#every-deployment-decides-three-things] + +| 決策 | 含義 | +|---|---| +| **執行哪些位元組** | 固定一個確切的映象。像 `latest` 這樣的滾動標籤可能在你不知情時被重新指向,確切的版本標籤在該版本被重新構建時也會如此;只有摘要不會。保留上一個版本:那就是回滾。 | +| **配置放在哪裡** | 放在映象和應用之外。資料庫 URL、金鑰和部署的身份都是在執行時注入的環境變數,絕不烘焙進映象。[環境變數](/docs/reference/environment-variables)就是契約。 | +| **哪個資料庫** | 生產環境執行在 PostgreSQL 上。沒有 `OS_DATABASE_URL` 時,執行時會在其主目錄下寫一個 SQLite 檔案,這適合一臺持久的主機,但在容器的臨時檔案系統上會丟失資料。 | + +## 應用如何進入 [#how-the-app-gets-in] + +Community 映象**不帶任何應用**。你把 `os build` 編譯出的產物 +`dist/objectstack.json` 交給它,有三種方式: + +| 方式 | 做法 | 何時使用 | +|---|---|---| +| **掛載** | 把檔案掛載到 `/srv/app/objectstack.json`,映象預設在那裡查詢 | 你完全不想構建映象 | +| **複製進你自己的映象** | 一個 `FROM` 官方映象的兩階段 Dockerfile | 你每個版本釋出一個自包含的映象 | +| **按 URL 指定** | `OS_ARTIFACT_URL`,可選帶一個 `#sha256=` 完整性固定值 | 你希望升級應用就是一次環境變更加一次重啟 | + +[Docker](/docs/deploy/docker#run-it) 展示了全部三種方式。完全沒有產物時,執行時 +會啟動一個空核心,應用從 Console 市場安裝。 + +## 指南 [#guides] + +- [Docker](/docs/deploy/docker) —— 執行映象、配置它、把它放到代理後面、升級它。 +- [Kubernetes](/docs/deploy/kubernetes) —— 在編排器上運行同一個映象,以及需要保持的屬性。 +- [物理隔離](/docs/deploy/air-gapped) —— 僅限企業版:物理隔離是一種**許可證模式**,以及會在啟動時被拒絕的組合。 + +沒有容器執行時的話,同一個產物可以在裝有 Node.js 22 的主機上通過 `os start` +執行 —— 參見[在本地執行 ObjectOS Community](/docs/start/community)。 + +## 企業版額外提供 [#enterprise-adds] + +### 另外兩個決策:許可證和雲端姿態 [#two-more-decisions-license-and-cloud-posture] + +| 決策 | 含義 | +|---|---| +| **哪種許可證** | 許可證是正常路徑的一部分,而不是附加項。有些部署形態沒有許可證會*拒絕啟動*。 | +| **哪種雲端姿態** | 這個執行時是否與控制平面通訊。它**與許可證模式耦合**,不受支援的組合會在啟動時被拒絕,而不是悄悄降級。 | + +實踐中這兩者是同一個決策,而選錯的後果是啟動失敗,而不是緩慢的洩漏。 +[物理隔離](/docs/deploy/air-gapped)完整列出了受支援的組合 —— 即使你的站點是聯網 +的也請讀一讀,因為正是那一頁解釋了這些組合*為什麼*是現在這樣。 + +### 部署包 [#the-deploy-bundle] + +你的版本會附帶一個**部署包**:一個 Compose 技術棧、每種受支援形態的環境模板, +以及一份 README,其中有適用於你的版本的確切命令 —— 包括映象倉庫引用、摘要和供應 +鏈校驗步驟。 + +這些頁面承載的是**決策** —— 為什麼用摘要、哪種形態用哪種許可證模式、多節點使 +什麼成為必需、執行時會拒絕什麼。部署包承載的是**你要複製的值**。當兩者看起來 +不一致時,以隨你的映象釋出的部署包為準:它與它所配置的執行時一同版本化。 + +### 三種執行形態 [#three-run-shapes] + +企業版映象可以用三種方式執行應用,而這個選擇會影響你能用什麼 —— 它不僅僅是 +打包方式的問題。 + +| 形態 | 應用來自 | 何時使用 | +|---|---|---| +| **配置編寫** | 映象自帶的應用 —— 映象中編寫的後設資料,加上安裝進它的任何內容 | 單組織部署;Compose 技術棧配置的預設形態,也是不需要任何配置的形態。 | +| **產物固定** | 一個按 URL 指定的已釋出應用產物,在任何部署配置執行之前解析 | 你執行一個已釋出的應用,並希望升級應用就是一次環境變更加一次重啟,無需重建映象。注意,在這條路徑上部署的企業外掛**不會**載入。 | +| **組合** | 一個與本部署的企業外掛*組合*在一起的已釋出應用產物 | 多個組織共享一個數據庫。這是託管的單應用 SaaS 形態 —— 並且**僅限物理隔離許可證**,在啟動時強制執行。 | + +每種形態在部署包中都有自己的環境模板。複製適用於你的形態的那一個,而不是改寫 +另一個:這些模板的不同之處,正是決定姿態的那些值,而它們恰恰是執行時在提供任何 +服務之前會檢查的值。 diff --git a/content/docs/deploy/kubernetes.zh-Hans.mdx b/content/docs/deploy/kubernetes.zh-Hans.mdx new file mode 100644 index 00000000..7f60b83e --- /dev/null +++ b/content/docs/deploy/kubernetes.zh-Hans.mdx @@ -0,0 +1,142 @@ +--- +title: Kubernetes +seoTitle: "Kubernetes:探针、密钥、固定版本和迁移" +description: 在 Kubernetes 上运行 ObjectOS 镜像 —— 一个参考 Deployment、在正确端点上的正确探针、来自 Secret 的密钥,以及多副本企业版部署必须保持的属性。 +editions: [community, enterprise] +translation: + source_sha: 2270aa161805c5ab5a80c2c48562ac8ab6c54573019e3c7d17acc51587a2d6c0 + guide_rev: 1 + mode: auto +--- + +**没有 Helm chart,也没有官方清单集。**镜像在 Kubernetes 上无需修改即可运行, +所以本页给出一个最小参考,以及你的清单必须保持的属性 —— 每一项都是运行时所依赖 +或强制执行的东西。[Docker](/docs/deploy/docker) 上的一切都同样适用:同一个镜像、 +同样的变量、同样的升级规则。 + +## 参考 Deployment [#a-reference-deployment] + +Community 镜像的一个副本,密钥来自一个 `Secret`,每个探针各有自己的端点: + +```yaml title="deployment.yaml" +apiVersion: apps/v1 +kind: Deployment +metadata: + name: objectos +spec: + replicas: 1 + selector: + matchLabels: { app: objectos } + template: + metadata: + labels: { app: objectos } + spec: + containers: + - name: objectos + image: registry.example.com/crm:2.2.2 # your image FROM ghcr.io/objectstack-ai/objectstack:X.Y.Z + ports: + - containerPort: 8080 + envFrom: + - secretRef: + name: objectos-secrets # OS_DATABASE_URL, OS_AUTH_SECRET, OS_SECRET_KEY + livenessProbe: + httpGet: { path: /api/v1/health, port: 8080 } + periodSeconds: 30 + readinessProbe: + httpGet: { path: /api/v1/ready, port: 8080 } + initialDelaySeconds: 5 + periodSeconds: 10 +--- +apiVersion: v1 +kind: Service +metadata: + name: objectos +spec: + selector: { app: objectos } + ports: + - port: 80 + targetPort: 8080 +``` + +## 需要保持的属性 [#properties-to-preserve] + +### 镜像被精确固定 [#the-image-is-pinned-exactly] + +按 `@sha256:` 摘要引用镜像,并在旁边写上读取它时对应的确切版本,绝不使用滚动标签。 +在编排器上,这比在单台主机上更重要:拉取策略加上会移动的标签,意味着"同一个" +Deployment 的两个 Pod 可能运行不同的构建。滚动标签随每次发布而移动,确切的版本 +标签在该版本被重新构建时也会移动;参见[Docker → 固定镜像](/docs/deploy/docker#pin-the-image)。 + +### 探针指向正确的端点 [#probes-address-the-right-endpoints] + +`/api/v1/health` 是存活探针。`/api/v1/ready` 是就绪探针。在运行时启动期间、关闭 +期间,以及任何数据驱动停止响应时,就绪端点都会返回 **503** —— 这样滚动重启可以 +干净地排空,而失去数据库的 Pod 会离开 Service 的端点,而不是返回错误。 + +不要把就绪检查放到存活探针上。否则一次数据库故障会同时重启所有 Pod,而这并不能 +让数据库恢复,配置故障也会表现为 `CrashLoopBackOff`,而不是故障本身。数据库恢复 +后,Pod 会自行恢复:就绪探针在数据库不可用时把它们排空,之后再重新接纳它们。 + +### 密钥来自 Secret [#secrets-come-from-a-secret] + +`OS_AUTH_SECRET` 和 `OS_SECRET_KEY` 应放在一个 `Secret` 中,只生成一次,并由每个 +Pod 挂载 —— 绝不按 Pod 生成,绝不烘焙进镜像。一个自己生成加密密钥的 Pod 会在下次 +重新调度时丢失它,用它加密的所有密钥也随之丢失。 + +### 业务数据存放在集群之外 [#business-data-lives-outside-the-cluster] + +把部署指向一个托管的 PostgreSQL。集群内的持久卷并不是业务数据该待的地方。 + +### Community 上只用一个副本 [#one-replica-on-community] + +Community 上的协调是进程内的,所以保持 `replicas: 1`。声明更多副本 +(`OS_CLUSTER_REPLICAS` 大于 1)在没有许可证时会拒绝启动。多副本是一项企业版能力 +—— 见下文。 + +### 入口 [#ingress] + +在边缘终止 TLS,并把 `OS_AUTH_URL` 和 `OS_TRUSTED_ORIGINS` 设为公网地址。在 +`OS_CORS_ORIGIN` 中显式列出跨源前端;绝不把通配符源与携带凭据的请求一起使用。当 +运行时位于代理之后时,确保代理用它自己的 forwarded-for 头替换客户端提供的任何同类 +头 —— 速率限制和审计轨迹都依赖于调用者身份是可信的。 + +## 企业版额外提供 [#enterprise-adds] + +**我们提供的是 Compose 技术栈,而不是 Helm chart 或 Kubernetes 清单。**你的版本中 +附带的部署包才是受支持、经过测试的配置。在 Kubernetes 上,清单要你自己组装,所以 +也要重现该技术栈的这些属性。编排器不会改变部署有权获得的任何东西。 + +### 镜像按摘要固定 [#the-image-is-pinned-by-digest] + +在 Pod spec 中按 `@sha256:` 摘要引用企业版镜像。拉取镜像需要一个保存你的许可证 +镜像仓库凭据的拉取密钥。 + +### 迁移在副本提供服务之前运行一次,并运行到完成 [#migrations-run-once-to-completion-before-replicas-serve] + +Compose 技术栈运行一个一次性的迁移容器,它必须在任何应用副本启动之前完成。在 +Kubernetes 上,这是一个 Job(或等效的服务前钩子),而不是每个副本上的 init 容器 +—— 关键在于模式变更只运行**一次**,而不是每个副本并行各运行一次。镜像正是为此 +支持一种"迁移后退出"的模式;部署包中点名了对应的变量。 + +### 每个副本都使用完全相同的密钥 [#every-replica-gets-identical-secrets] + +多副本不只是调高副本数: + +- 必须开启**集群驱动**,并以 Redis 作为后端。 +- **共享的加密密钥**成为必需。没有集群驱动时,每个副本会自己生成一个;跨副本时 + 它们会互不相同,而**运行时会拒绝启动**,而不是让它们分叉。一个值,挂载到每个 + 副本中。 +- **认证密钥**同样必须在各副本之间完全相同。 + +### 许可证和云端姿态作用于整个部署 [#license-and-cloud-posture-are-deployment-wide] + +许可证密钥、许可证模式和云端姿态设置都是部署身份的一部分。它们在每个副本中都 +必须相同,其受支持的组合会**在启动时**检查 —— 不匹配的 Pod 不会降级运行,而是 +直接退出。组合参见[物理隔离](/docs/deploy/air-gapped)。 + +### 滚动升级就是更换摘要 [#rolling-upgrades-change-the-digest] + +升级就是更换镜像摘要,并让迁移 Job 在新副本提供服务之前运行到完成。模式迁移只能 +向前:回滚镜像并不会回滚模式,所以先做备份,并保留上一个摘要以便回滚。应用本身 +如果以已发布产物的形式交付,则独立于镜像进行版本管理 —— 参见 +[部署](/docs/deploy#three-run-shapes)中的运行形态。 diff --git a/content/docs/deploy/kubernetes.zh-Hant.mdx b/content/docs/deploy/kubernetes.zh-Hant.mdx new file mode 100644 index 00000000..8a7ea031 --- /dev/null +++ b/content/docs/deploy/kubernetes.zh-Hant.mdx @@ -0,0 +1,143 @@ +--- +# @generated zh-Hant from zh-Hans -- do not edit. Edit the English source, then regenerate: pnpm --filter @objectos/docs gen:zh-hant +title: Kubernetes +seoTitle: "Kubernetes:探針、金鑰、固定版本和遷移" +description: 在 Kubernetes 上執行 ObjectOS 映象 —— 一個參考 Deployment、在正確端點上的正確探針、來自 Secret 的金鑰,以及多副本企業版部署必須保持的屬性。 +editions: [community, enterprise] +translation: + source_sha: 2270aa161805c5ab5a80c2c48562ac8ab6c54573019e3c7d17acc51587a2d6c0 + guide_rev: 1 + mode: auto +--- + +**沒有 Helm chart,也沒有官方清單集。**映象在 Kubernetes 上無需修改即可執行, +所以本頁給出一個最小參考,以及你的清單必須保持的屬性 —— 每一項都是執行時所依賴 +或強制執行的東西。[Docker](/docs/deploy/docker) 上的一切都同樣適用:同一個映象、 +同樣的變數、同樣的升級規則。 + +## 參考 Deployment [#a-reference-deployment] + +Community 映象的一個副本,金鑰來自一個 `Secret`,每個探針各有自己的端點: + +```yaml title="deployment.yaml" +apiVersion: apps/v1 +kind: Deployment +metadata: + name: objectos +spec: + replicas: 1 + selector: + matchLabels: { app: objectos } + template: + metadata: + labels: { app: objectos } + spec: + containers: + - name: objectos + image: registry.example.com/crm:2.2.2 # your image FROM ghcr.io/objectstack-ai/objectstack:X.Y.Z + ports: + - containerPort: 8080 + envFrom: + - secretRef: + name: objectos-secrets # OS_DATABASE_URL, OS_AUTH_SECRET, OS_SECRET_KEY + livenessProbe: + httpGet: { path: /api/v1/health, port: 8080 } + periodSeconds: 30 + readinessProbe: + httpGet: { path: /api/v1/ready, port: 8080 } + initialDelaySeconds: 5 + periodSeconds: 10 +--- +apiVersion: v1 +kind: Service +metadata: + name: objectos +spec: + selector: { app: objectos } + ports: + - port: 80 + targetPort: 8080 +``` + +## 需要保持的屬性 [#properties-to-preserve] + +### 映象被精確固定 [#the-image-is-pinned-exactly] + +按 `@sha256:` 摘要引用映象,並在旁邊寫上讀取它時對應的確切版本,絕不使用滾動標籤。 +在編排器上,這比在單臺主機上更重要:拉取策略加上會移動的標籤,意味著"同一個" +Deployment 的兩個 Pod 可能執行不同的構建。滾動標籤隨每次釋出而移動,確切的版本 +標籤在該版本被重新構建時也會移動;參見[Docker → 固定映象](/docs/deploy/docker#pin-the-image)。 + +### 探針指向正確的端點 [#probes-address-the-right-endpoints] + +`/api/v1/health` 是存活探針。`/api/v1/ready` 是就緒探針。在執行時啟動期間、關閉 +期間,以及任何資料驅動停止響應時,就緒端點都會返回 **503** —— 這樣滾動重啟可以 +乾淨地排空,而失去資料庫的 Pod 會離開 Service 的端點,而不是返回錯誤。 + +不要把就緒檢查放到存活探針上。否則一次資料庫故障會同時重啟所有 Pod,而這並不能 +讓資料庫恢復,配置故障也會表現為 `CrashLoopBackOff`,而不是故障本身。資料庫恢復 +後,Pod 會自行恢復:就緒探針在資料庫不可用時把它們排空,之後再重新接納它們。 + +### 金鑰來自 Secret [#secrets-come-from-a-secret] + +`OS_AUTH_SECRET` 和 `OS_SECRET_KEY` 應放在一個 `Secret` 中,只生成一次,並由每個 +Pod 掛載 —— 絕不按 Pod 生成,絕不烘焙進映象。一個自己生成加密金鑰的 Pod 會在下次 +重新排程時丟失它,用它加密的所有金鑰也隨之丟失。 + +### 業務資料存放在叢集之外 [#business-data-lives-outside-the-cluster] + +把部署指向一個託管的 PostgreSQL。叢集內的持久卷並不是業務資料該待的地方。 + +### Community 上只用一個副本 [#one-replica-on-community] + +Community 上的協調是程序內的,所以保持 `replicas: 1`。宣告更多副本 +(`OS_CLUSTER_REPLICAS` 大於 1)在沒有許可證時會拒絕啟動。多副本是一項企業版能力 +—— 見下文。 + +### 入口 [#ingress] + +在邊緣終止 TLS,並把 `OS_AUTH_URL` 和 `OS_TRUSTED_ORIGINS` 設為公網地址。在 +`OS_CORS_ORIGIN` 中顯式列出跨源前端;絕不把萬用字元源與攜帶憑據的請求一起使用。當 +執行時位於代理之後時,確保代理用它自己的 forwarded-for 頭替換客戶端提供的任何同類 +頭 —— 速率限制和審計軌跡都依賴於呼叫者身份是可信的。 + +## 企業版額外提供 [#enterprise-adds] + +**我們提供的是 Compose 技術棧,而不是 Helm chart 或 Kubernetes 清單。**你的版本中 +附帶的部署包才是受支援、經過測試的配置。在 Kubernetes 上,清單要你自己組裝,所以 +也要重現該技術棧的這些屬性。編排器不會改變部署有權獲得的任何東西。 + +### 映象按摘要固定 [#the-image-is-pinned-by-digest] + +在 Pod spec 中按 `@sha256:` 摘要引用企業版映象。拉取映象需要一個儲存你的許可證 +映象倉庫憑據的拉取金鑰。 + +### 遷移在副本提供服務之前執行一次,並執行到完成 [#migrations-run-once-to-completion-before-replicas-serve] + +Compose 技術棧執行一個一次性的遷移容器,它必須在任何應用副本啟動之前完成。在 +Kubernetes 上,這是一個 Job(或等效的服務前鉤子),而不是每個副本上的 init 容器 +—— 關鍵在於模式變更只執行**一次**,而不是每個副本並行各執行一次。映象正是為此 +支援一種"遷移後退出"的模式;部署包中點名了對應的變數。 + +### 每個副本都使用完全相同的金鑰 [#every-replica-gets-identical-secrets] + +多副本不只是調高副本數: + +- 必須開啟**叢集驅動**,並以 Redis 作為後端。 +- **共享的加密金鑰**成為必需。沒有叢集驅動時,每個副本會自己生成一個;跨副本時 + 它們會互不相同,而**執行時會拒絕啟動**,而不是讓它們分叉。一個值,掛載到每個 + 副本中。 +- **認證金鑰**同樣必須在各副本之間完全相同。 + +### 許可證和雲端姿態作用於整個部署 [#license-and-cloud-posture-are-deployment-wide] + +許可證金鑰、許可證模式和雲端姿態設定都是部署身份的一部分。它們在每個副本中都 +必須相同,其受支援的組合會**在啟動時**檢查 —— 不匹配的 Pod 不會降級執行,而是 +直接退出。組合參見[物理隔離](/docs/deploy/air-gapped)。 + +### 滾動升級就是更換摘要 [#rolling-upgrades-change-the-digest] + +升級就是更換映象摘要,並讓遷移 Job 在新副本提供服務之前執行到完成。模式遷移只能 +向前:回滾映象並不會回滾模式,所以先做備份,並保留上一個摘要以便回滾。應用本身 +如果以已釋出產物的形式交付,則獨立於映象進行版本管理 —— 參見 +[部署](/docs/deploy#three-run-shapes)中的執行形態。 diff --git a/content/docs/operate/audit-logs.zh-Hans.mdx b/content/docs/operate/audit-logs.zh-Hans.mdx new file mode 100644 index 00000000..bb871b84 --- /dev/null +++ b/content/docs/operate/audit-logs.zh-Hans.mdx @@ -0,0 +1,142 @@ +--- +title: 审计日志 +seoTitle: "审计日志:每一次变更、登录和事件" +description: 阅读不可变的审计轨迹和诊断日志,它们记录了你的部署上的每一次变更、每一次登录和每一个发出的事件。 +editions: [community, enterprise] +translation: + source_sha: 8c6b53d05235befa452e3b9a3953df31e5b1ba4c01aaf168a9392a49da21197f + guide_rev: 1 + mode: auto +--- + +审计日志是你的部署上所发生之事的不可变记录:谁、在什么时候、从哪里改了什么。本页 +是 **Setup → Diagnostics** 下各诊断界面的深入参考。关于日志、指标和请求身份的 +运维概览,参见[可观测性](/docs/operate/observability)。 + +> 这些界面需要 `manage_platform_settings` 权限。 + +当你需要证明或调查以下事项时,就去查审计日志: + +- 权限敏感的变更; +- 设置和配置变更; +- 用户和会话活动; +- 集成和自动化活动; +- 访问被拒绝的分析。 + +## 审计日志 [#audit-logs] + +位于 **Setup → Diagnostics → Audit Logs**(`sys_audit_log`)。该对象是**只读且 +不可变的** —— 一条只追加的平台事件轨迹。你可以过滤、分组和查看行,但在 Setup 中 +没有任何东西能编辑或删除它们。 + +### 每个读者能看到什么 [#what-each-reader-is-served] + +每一行都会保持存储,但从 ObjectStack 17.6 起,只有当读者自己能读取某条记录时, +才会向其返回关于这条记录的行 —— 判断依据与决定他们能否打开这条记录的对象权限、 +共享和行级安全相同。这会排除: + +- 每一条 `delete` 行,以及关于此后已被删除的记录的其他每一行; +- 登出行,以及其会话此后已被移除的登录行; +- 关于读者无法打开的记录的行。 + +与任何记录无关的行 —— 配置变更、用户导入运行、不带会话的认证事件 —— 与以前一样, +按该日志自身的授权返回。 + +**`view_all_audit_log`** 能力会解除这种收窄(ObjectStack 17.7):持有者可以看到 +删除和登出轨迹,以及关于他们无法打开的记录的行。平台管理员默认持有它;组织管理员 +则不持有,所以请把它显式授予其他必须阅读完整轨迹的人。 + +字段级安全在每一行内部仍然适用。`old_value` 和 `new_value` 只向能以未掩码形式读取 +某个字段的读者返回该字段,并且在它们上面过滤、排序或分组会被拒绝(`403`),除非 +过滤条件以相等方式指定了一个对象(`object_name`),且你能读取它的每个字段。 + +### 预设视图 [#preset-views] + +列表打开时带有按动作类别过滤的标签页。每个标签页都应用上述规则: + +| 视图 | 显示 | +|---|---| +| `Recent` | 所有事件,最新的在前 | +| `Writes` | 创建 / 更新 / 删除动作 —— `delete` 行仅在持有 `view_all_audit_log` 时显示 | +| `Auth` | 登录和登出事件 —— 登出,以及其会话此后已被移除的登录,仅在持有 `view_all_audit_log` 时显示 | +| `Config` | 设置和配置变更 | + +### 列与字段 [#columns-and-fields] + +默认列为 `Timestamp`、`Action`(一个彩色标签 —— create / update / …)、`Object`、 +`Record ID` 和 `Actor`。页脚显示记录总数(例如 *36 records*)。 + +每一行都带有完整的字段集: + +| 字段 | 含义 | +|---|---| +| `created_at` | 事件被记录的时间 | +| `action` | 操作(create、update、delete、auth……) | +| `user_id` | 指向执行操作的用户的查找 | +| `actor` | 文本形式的执行者身份 | +| `object_name` | 事件涉及的对象(例如 `sys_user`) | +| `record_id` | 受影响的记录 | +| `old_value` | 之前的值(textarea) | +| `new_value` | 新的值(textarea) | +| `ip_address` | 请求的来源 IP | +| `user_agent` | 客户端的 user agent | +| `tenant_id` | 所属租户 | +| `metadata` | 额外的结构化上下文 | + +> `Config` 视图是回答"谁改了这个设置?"的最快方式 —— 它在你开始阅读行之前,就把 +> 轨迹收窄到配置动作。 + +### 解读一次变更 [#reading-a-change] + +对于写入动作,值得关注的细节是变更前后。打开某一行的 `⋮` 菜单,展开 `old_value` +和 `new_value`,查看这条记录上改了什么 —— 你能读取的每个字段都完整显示,其他的 +一概不显示。`Object` 和 `Record ID` 列告诉你*什么*被改动了; +`old_value`/`new_value` 告诉你*如何*改动;`actor`、`ip_address` 和 `user_agent` +告诉你*谁*改的,以及*从哪里*改的。 + +> 对于受监管的环境,请在存储层为不可变性提供保障 —— 在数据库层把表设为只追加, +> 并定义保留策略。 + +## 通知事件 [#notification-events] + +位于 **Setup → Diagnostics → Notification Events**(`sys_notification`)。这是 +**通知系统的入口日志** —— 每次 `emit()` 一行,在任何投递决策之前写入。用它来诊断 +一条通知为什么到达或没有到达收件箱;之后的路由和物化过程记录在 +[通知](/docs/configure/notifications)中。 + +预设视图为 `Recent` 和 `By Topic`。默认列:`Topic`(例如 `project.digest`)、 +`Severity`(一个 info/warn/… 标签)、`Actor`、`Source Object` 和 `Created At`。 + +| 字段 | 含义 | +|---|---| +| `topic` | 发出的主题 | +| `payload` | 事件载荷(JSON) | +| `severity` | 严重级别 | +| `dedup_key` | 用于合并重复发出的键 | +| `source_object` | 引发事件的对象 | +| `source_id` | 引发事件的记录 | +| `actor_id` | 触发发出的主体 | +| `created_at` | 事件被接收的时间 | + +`dedup_key` 会合并重复的发出,`payload` 携带模板渲染所依据的原始事件上下文。由于 +这个日志位于路由的*上游*,如果这里有一行却没有对应的收件箱消息,通常意味着受众没有 +解析到那个用户,或者他们的偏好在那个渠道上屏蔽了该主题 —— 当用户报告缺少提醒时, +请查看[通知](/docs/configure/notifications)。 + +在下游,每个事件都会物化为按用户的收件箱消息(`sys_inbox_message`)—— 接收方的 +视角见[通知](/docs/use/notifications)。 + +## 会话 [#sessions] + +位于 **Setup → Diagnostics → Sessions**。会话列出活动的和最近的登录会话,因此你 +可以调查某个用户的活动或撤销其访问。审查登录事件时,把它与 `Auth` 审计视图搭配 +使用。 + +## 下一步 [#where-to-go-next] + +| 任务 | 页面 | +|---|---| +| 日志、指标和请求身份的运维概览 | [可观测性](/docs/operate/observability) | +| 了解通知如何被路由和渲染 | [通知](/docs/configure/notifications) | +| 在上线前加固部署 | [生产就绪](/docs/operate/production) | +| 回到运维概览 | [运维](/docs/operate) | diff --git a/content/docs/operate/audit-logs.zh-Hant.mdx b/content/docs/operate/audit-logs.zh-Hant.mdx new file mode 100644 index 00000000..63a63b6c --- /dev/null +++ b/content/docs/operate/audit-logs.zh-Hant.mdx @@ -0,0 +1,143 @@ +--- +# @generated zh-Hant from zh-Hans -- do not edit. Edit the English source, then regenerate: pnpm --filter @objectos/docs gen:zh-hant +title: 審計日誌 +seoTitle: "審計日誌:每一次變更、登入和事件" +description: 閱讀不可變的審計軌跡和診斷日誌,它們記錄了你的部署上的每一次變更、每一次登入和每一個發出的事件。 +editions: [community, enterprise] +translation: + source_sha: 8c6b53d05235befa452e3b9a3953df31e5b1ba4c01aaf168a9392a49da21197f + guide_rev: 1 + mode: auto +--- + +審計日誌是你的部署上所發生之事的不可變記錄:誰、在什麼時候、從哪裡改了什麼。本頁 +是 **Setup → Diagnostics** 下各診斷介面的深入參考。關於日誌、指標和請求身份的 +運維概覽,參見[可觀測性](/docs/operate/observability)。 + +> 這些介面需要 `manage_platform_settings` 許可權。 + +當你需要證明或調查以下事項時,就去查審計日誌: + +- 許可權敏感的變更; +- 設定和配置變更; +- 使用者和會話活動; +- 整合和自動化活動; +- 訪問被拒絕的分析。 + +## 審計日誌 [#audit-logs] + +位於 **Setup → Diagnostics → Audit Logs**(`sys_audit_log`)。該物件是**只讀且 +不可變的** —— 一條只追加的平臺事件軌跡。你可以過濾、分組和檢視行,但在 Setup 中 +沒有任何東西能編輯或刪除它們。 + +### 每個讀者能看到什麼 [#what-each-reader-is-served] + +每一行都會保持儲存,但從 ObjectStack 17.6 起,只有當讀者自己能讀取某條記錄時, +才會向其返回關於這條記錄的行 —— 判斷依據與決定他們能否開啟這條記錄的物件許可權、 +共享和行級安全相同。這會排除: + +- 每一條 `delete` 行,以及關於此後已被刪除的記錄的其他每一行; +- 登出行,以及其會話此後已被移除的登入行; +- 關於讀者無法開啟的記錄的行。 + +與任何記錄無關的行 —— 配置變更、使用者匯入執行、不帶會話的認證事件 —— 與以前一樣, +按該日誌自身的授權返回。 + +**`view_all_audit_log`** 能力會解除這種收窄(ObjectStack 17.7):持有者可以看到 +刪除和登出軌跡,以及關於他們無法開啟的記錄的行。平臺管理員預設持有它;組織管理員 +則不持有,所以請把它顯式授予其他必須閱讀完整軌跡的人。 + +欄位級安全在每一行內部仍然適用。`old_value` 和 `new_value` 只向能以未掩碼形式讀取 +某個欄位的讀者返回該欄位,並且在它們上面過濾、排序或分組會被拒絕(`403`),除非 +過濾條件以相等方式指定了一個物件(`object_name`),且你能讀取它的每個欄位。 + +### 預設檢視 [#preset-views] + +列表開啟時帶有按動作類別過濾的標籤頁。每個標籤頁都應用上述規則: + +| 檢視 | 顯示 | +|---|---| +| `Recent` | 所有事件,最新的在前 | +| `Writes` | 建立 / 更新 / 刪除動作 —— `delete` 行僅在持有 `view_all_audit_log` 時顯示 | +| `Auth` | 登入和登出事件 —— 登出,以及其會話此後已被移除的登入,僅在持有 `view_all_audit_log` 時顯示 | +| `Config` | 設定和配置變更 | + +### 列與欄位 [#columns-and-fields] + +預設列為 `Timestamp`、`Action`(一個彩色標籤 —— create / update / …)、`Object`、 +`Record ID` 和 `Actor`。頁尾顯示記錄總數(例如 *36 records*)。 + +每一行都帶有完整的欄位集: + +| 欄位 | 含義 | +|---|---| +| `created_at` | 事件被記錄的時間 | +| `action` | 操作(create、update、delete、auth……) | +| `user_id` | 指向執行操作的使用者的查詢 | +| `actor` | 文本形式的執行者身份 | +| `object_name` | 事件涉及的物件(例如 `sys_user`) | +| `record_id` | 受影響的記錄 | +| `old_value` | 之前的值(textarea) | +| `new_value` | 新的值(textarea) | +| `ip_address` | 請求的來源 IP | +| `user_agent` | 客戶端的 user agent | +| `tenant_id` | 所屬租戶 | +| `metadata` | 額外的結構化上下文 | + +> `Config` 檢視是回答"誰改了這個設定?"的最快方式 —— 它在你開始閱讀行之前,就把 +> 軌跡收窄到配置動作。 + +### 解讀一次變更 [#reading-a-change] + +對於寫入動作,值得關注的細節是變更前後。開啟某一行的 `⋮` 選單,展開 `old_value` +和 `new_value`,檢視這條記錄上改了什麼 —— 你能讀取的每個欄位都完整顯示,其他的 +一概不顯示。`Object` 和 `Record ID` 列告訴你*什麼*被改動了; +`old_value`/`new_value` 告訴你*如何*改動;`actor`、`ip_address` 和 `user_agent` +告訴你*誰*改的,以及*從哪裡*改的。 + +> 對於受監管的環境,請在儲存層為不可變性提供保障 —— 在資料庫層把表設為只追加, +> 並定義保留策略。 + +## 通知事件 [#notification-events] + +位於 **Setup → Diagnostics → Notification Events**(`sys_notification`)。這是 +**通知系統的入口日誌** —— 每次 `emit()` 一行,在任何投遞決策之前寫入。用它來診斷 +一條通知為什麼到達或沒有到達收件箱;之後的路由和物化過程記錄在 +[通知](/docs/configure/notifications)中。 + +預設檢視為 `Recent` 和 `By Topic`。預設列:`Topic`(例如 `project.digest`)、 +`Severity`(一個 info/warn/… 標籤)、`Actor`、`Source Object` 和 `Created At`。 + +| 欄位 | 含義 | +|---|---| +| `topic` | 發出的主題 | +| `payload` | 事件載荷(JSON) | +| `severity` | 嚴重級別 | +| `dedup_key` | 用於合併重複發出的鍵 | +| `source_object` | 引發事件的物件 | +| `source_id` | 引發事件的記錄 | +| `actor_id` | 觸發發出的主體 | +| `created_at` | 事件被接收的時間 | + +`dedup_key` 會合並重復的發出,`payload` 攜帶模板渲染所依據的原始事件上下文。由於 +這個日誌位於路由的*上游*,如果這裡有一行卻沒有對應的收件箱訊息,通常意味著受眾沒有 +解析到那個使用者,或者他們的偏好在那個渠道上遮蔽了該主題 —— 當用戶報告缺少提醒時, +請檢視[通知](/docs/configure/notifications)。 + +在下游,每個事件都會物化為按使用者的收件箱訊息(`sys_inbox_message`)—— 接收方的 +視角見[通知](/docs/use/notifications)。 + +## 會話 [#sessions] + +位於 **Setup → Diagnostics → Sessions**。會話列出活動的和最近的登入會話,因此你 +可以調查某個使用者的活動或撤銷其訪問。審查登入事件時,把它與 `Auth` 審計檢視搭配 +使用。 + +## 下一步 [#where-to-go-next] + +| 任務 | 頁面 | +|---|---| +| 日誌、指標和請求身份的運維概覽 | [可觀測性](/docs/operate/observability) | +| 瞭解通知如何被路由和渲染 | [通知](/docs/configure/notifications) | +| 在上線前加固部署 | [生產就緒](/docs/operate/production) | +| 回到運維概覽 | [運維](/docs/operate) | diff --git a/content/docs/operate/index.zh-Hans.mdx b/content/docs/operate/index.zh-Hans.mdx new file mode 100644 index 00000000..b8bdd38d --- /dev/null +++ b/content/docs/operate/index.zh-Hans.mdx @@ -0,0 +1,45 @@ +--- +title: 运维 +seoTitle: "运维:生产就绪、备份、升级" +description: 让运行中的 ObjectOS 部署保持健康 —— 生产就绪、可观测性、审计轨迹、备份、升级和恢复。 +editions: [community, enterprise] +translation: + source_sha: a17ed607565693a65e32a25c8fe8db345aa917afac9348647fecca7a35e9c8f7 + guide_rev: 1 + mode: auto +--- + +本节讲的是让 ObjectOS **在生产环境中保持健康**。[配置](/docs/configure)涵盖的是 +设置阶段的管理 —— 用户、访问、登录、集成 —— 而*运维*涵盖的是上线后的日常工作: +加固部署、监视它、证明发生了什么,以及在出问题时恢复。 + +这里的每一页都适用于两个自管理版本,ObjectOS Community 和 ObjectOS Enterprise: +它们运行同一个运行时,所以运维方式也相同。当需许可证的企业版镜像能做得更多时 —— +其发布说明中点名的摘要、许可证密钥、多个副本 —— 页面会以**企业版额外提供**一节结尾。在 +ObjectOS Cloud 上,由我们为你运维部署。 + +## 运维页面 [#operations-pages] + +| 页面 | 用于 | +|---|---| +| [生产就绪](/docs/operate/production) | 在上线前后加固部署 | +| [可观测性](/docs/operate/observability) | 接好日志、请求 id、指标和错误 | +| [审计日志](/docs/operate/audit-logs) | 阅读不可变的审计轨迹和诊断日志 | +| [备份](/docs/operate/backup) | 规划备份和灾难恢复 | +| [升级](/docs/operate/upgrade) | 安全地升级或回滚 | +| [故障排查](/docs/operate/troubleshooting) | 诊断出故障的部署 | + +## 配置与运维 [#configure-vs-operate] + +设置阶段的管理 —— 添加用户、授予访问、接好邮件、存储和集成 —— 位于 +[配置](/docs/configure)之下。等部署上线、你需要观察、审计、备份或升级它时,再回到 +这里。 + +## 下一步 [#where-to-go-next] + +| 任务 | 页面 | +|---|---| +| 上线前检查清单 | [生产就绪](/docs/operate/production) | +| 追踪一次变更或一次登录 | [审计日志](/docs/operate/audit-logs) | +| 设置监控 | [可观测性](/docs/operate/observability) | +| 设置阶段的管理 | [配置](/docs/configure) | diff --git a/content/docs/operate/index.zh-Hant.mdx b/content/docs/operate/index.zh-Hant.mdx new file mode 100644 index 00000000..3acffd33 --- /dev/null +++ b/content/docs/operate/index.zh-Hant.mdx @@ -0,0 +1,46 @@ +--- +# @generated zh-Hant from zh-Hans -- do not edit. Edit the English source, then regenerate: pnpm --filter @objectos/docs gen:zh-hant +title: 運維 +seoTitle: "運維:生產就緒、備份、升級" +description: 讓執行中的 ObjectOS 部署保持健康 —— 生產就緒、可觀測性、審計軌跡、備份、升級和恢復。 +editions: [community, enterprise] +translation: + source_sha: a17ed607565693a65e32a25c8fe8db345aa917afac9348647fecca7a35e9c8f7 + guide_rev: 1 + mode: auto +--- + +本節講的是讓 ObjectOS **在生產環境中保持健康**。[配置](/docs/configure)涵蓋的是 +設定階段的管理 —— 使用者、訪問、登入、整合 —— 而*運維*涵蓋的是上線後的日常工作: +加固部署、監視它、證明發生了什麼,以及在出問題時恢復。 + +這裡的每一頁都適用於兩個自管理版本,ObjectOS Community 和 ObjectOS Enterprise: +它們運行同一個執行時,所以運維方式也相同。當需許可證的企業版映象能做得更多時 —— +其釋出說明中點名的摘要、許可證金鑰、多個副本 —— 頁面會以**企業版額外提供**一節結尾。在 +ObjectOS Cloud 上,由我們為你運維部署。 + +## 運維頁面 [#operations-pages] + +| 頁面 | 用於 | +|---|---| +| [生產就緒](/docs/operate/production) | 在上線前後加固部署 | +| [可觀測性](/docs/operate/observability) | 接好日誌、請求 id、指標和錯誤 | +| [審計日誌](/docs/operate/audit-logs) | 閱讀不可變的審計軌跡和診斷日誌 | +| [備份](/docs/operate/backup) | 規劃備份和災難恢復 | +| [升級](/docs/operate/upgrade) | 安全地升級或回滾 | +| [故障排查](/docs/operate/troubleshooting) | 診斷出故障的部署 | + +## 配置與運維 [#configure-vs-operate] + +設定階段的管理 —— 新增使用者、授予訪問、接好郵件、儲存和整合 —— 位於 +[配置](/docs/configure)之下。等部署上線、你需要觀察、審計、備份或升級它時,再回到 +這裡。 + +## 下一步 [#where-to-go-next] + +| 任務 | 頁面 | +|---|---| +| 上線前檢查清單 | [生產就緒](/docs/operate/production) | +| 追蹤一次變更或一次登入 | [審計日誌](/docs/operate/audit-logs) | +| 設定監控 | [可觀測性](/docs/operate/observability) | +| 設定階段的管理 | [配置](/docs/configure) | diff --git a/content/docs/operate/observability.zh-Hans.mdx b/content/docs/operate/observability.zh-Hans.mdx new file mode 100644 index 00000000..c11ce91d --- /dev/null +++ b/content/docs/operate/observability.zh-Hans.mdx @@ -0,0 +1,117 @@ +--- +title: 可观测性 +seoTitle: "可观测性:日志、请求 ID、指标、错误" +description: 日志、请求 id、指标、错误、会话和审计日志。 +editions: [community, enterprise] +translation: + source_sha: 6260a1763af40fdb7320387d9d4a60879fc6f529e51b110ee98e9a10dff64f6e + guide_rev: 1 + mode: auto +--- + +ObjectOS 的运维既需要基础设施信号,也需要应用信号。框架提供请求 id、指标、错误 +上报器和审计原语;由部署决定把它们导出到哪里。 + +## 请求身份 [#request-identity] + +每个生产部署都应传递或生成一个请求 id: + +```text +X-Request-Id +``` + +用它来关联: + +- 入口日志; +- ObjectOS 日志; +- 数据库慢查询; +- 错误报告; +- 客户支持工单。 + +## 指标和错误 [#metrics-and-errors] + +运行时在进程内保存指标和错误报告,只有在你选择了导出器时才会导出它们。至少要 +跟踪: + +| 信号 | 原因 | +|---|---| +| 按路由/状态统计的请求数 | 发现错误激增 | +| 请求耗时 | 发现延迟回退 | +| 5xx 错误 | 对运行时故障告警 | +| 就绪状态转换 | 进程只有在其内核构建完成后才报告就绪,并且当某个数据驱动停止时,`/api/v1/ready` 返回 503 | + +请求数和耗时以 `http_requests_total` 和 `http_request_duration_ms` 发出,带有方法、 +路由模式和状态标签,覆盖每一个入站请求 —— 包括登录和 REST 数据 API。 + +### 用 OpenTelemetry 导出 [#export-with-opentelemetry] + +指标通过 **OTLP** 离开运行时。设置 `OS_OBS_EXPORTER=otlp` **和** +`OS_OTLP_ENDPOINT`(例如 `https:///otlp`)。导出器默认为 `noop`(零 +运行时开销),所以导出需要主动开启 —— 只设置端点而不设置 `OS_OBS_EXPORTER=otlp` +不会发出任何东西,而只设置 `otlp` 却没有端点会发出警告并回落到 `noop`。本地调试时 +使用 `OS_OBS_EXPORTER=console` 或 `json`,用 `OS_OBS_DEPLOYMENT_ENV` 给部署打标签 +(默认为 `production`)。其余变量列在 +[环境变量](/docs/reference/environment-variables#observability)中。 + +运行时本身不提供抓取端点。要使用 Prometheus,就把导出器指向一个 OpenTelemetry +Collector,让 Collector 把指标暴露给 Prometheus —— 或者把它们发送到任何接受 OTLP +的后端,例如 Grafana Cloud 或 Datadog。 + +一条入门告警,假设你的管道保留了运行时的指标名称: + +```yaml +groups: + - name: objectos + rules: + - alert: ObjectOS5xxSpike + expr: | + sum(rate(http_requests_total{status=~"5.."}[5m])) + / sum(rate(http_requests_total[5m])) > 0.02 + for: 5m + annotations: + summary: "ObjectOS 5xx rate above 2% for 5 minutes" +``` + +对 `http_requests_total{status=~"5.."}` 告警,而不是对 `http_request_errors_total`: +那个序列已在 ObjectStack 17.2 中退役,读数恒为零,看起来就像一个健康的服务器。 + +冷启动不在这个列表上。内核只在启动时构建一次,进程在那之后才报告 +[就绪](/docs/deploy/docker#health-checks) —— 没有稳态的缓存未命中信号可以告警, +所以改为对就绪状态告警。 + +认证失败也不在上面。运行时的指标注册表覆盖 HTTP、存储、缓存、后台作业和包注册表; +其中没有一项会统计被拒绝的凭据,所以没有可以告警的序列。从 `http_requests_total` +中统计 401/403 也不能替代它:被拒绝的 API 密钥与一个根本没有携带凭据的请求无法 +区分,而且这两种状态码中占绝大多数的,是针对已经正常认证的用户的普通权限拒绝。 +请改为以 `view_all_audit_log` 持有者的身份,在 Setup 的 `Auth` 审计视图中审查登录 +活动 —— 平台管理员默认持有它。没有它的话,登出以及其会话此后已被移除的登录都不会 +显示;参见[审计日志](/docs/operate/audit-logs#what-each-reader-is-served)。 + +## 审计日志 [#audit-logs] + +启用审计能力后,ObjectOS 会把审计记录写入 `sys_audit_log`。 + +审计日志用于: + +- 权限敏感的变更; +- 设置变更; +- 用户/会话调查; +- 集成活动; +- 访问被拒绝的分析。 + +对于受监管的环境,请在数据库或存储层把审计表设为只追加,并定义保留策略。 + +完整的字段参考、预设视图以及同类的诊断日志,参见 +[审计日志](/docs/operate/audit-logs)。 + +## Setup 诊断 [#setup-diagnostics] + +Setup 提供以下运维界面: + +- 会话; +- 审计日志; +- 通知; +- 启用 webhook 时的 Webhook 投递记录; +- 系统和安全概览仪表盘。 + +这些界面面向运维人员和支持工程师,而不仅仅是开发者。 diff --git a/content/docs/operate/observability.zh-Hant.mdx b/content/docs/operate/observability.zh-Hant.mdx new file mode 100644 index 00000000..5e6b255e --- /dev/null +++ b/content/docs/operate/observability.zh-Hant.mdx @@ -0,0 +1,118 @@ +--- +# @generated zh-Hant from zh-Hans -- do not edit. Edit the English source, then regenerate: pnpm --filter @objectos/docs gen:zh-hant +title: 可觀測性 +seoTitle: "可觀測性:日誌、請求 ID、指標、錯誤" +description: 日誌、請求 id、指標、錯誤、會話和審計日誌。 +editions: [community, enterprise] +translation: + source_sha: 6260a1763af40fdb7320387d9d4a60879fc6f529e51b110ee98e9a10dff64f6e + guide_rev: 1 + mode: auto +--- + +ObjectOS 的運維既需要基礎設施訊號,也需要應用訊號。框架提供請求 id、指標、錯誤 +上報器和審計原語;由部署決定把它們匯出到哪裡。 + +## 請求身份 [#request-identity] + +每個生產部署都應傳遞或生成一個請求 id: + +```text +X-Request-Id +``` + +用它來關聯: + +- 入口日誌; +- ObjectOS 日誌; +- 資料庫慢查詢; +- 錯誤報告; +- 客戶支援工單。 + +## 指標和錯誤 [#metrics-and-errors] + +執行時在程序內儲存指標和錯誤報告,只有在你選擇了匯出器時才會匯出它們。至少要 +跟蹤: + +| 訊號 | 原因 | +|---|---| +| 按路由/狀態統計的請求數 | 發現錯誤激增 | +| 請求耗時 | 發現延遲迴退 | +| 5xx 錯誤 | 對執行時故障告警 | +| 就緒狀態轉換 | 程序只有在其核心構建完成後才報告就緒,並且當某個資料驅動停止時,`/api/v1/ready` 返回 503 | + +請求數和耗時以 `http_requests_total` 和 `http_request_duration_ms` 發出,帶有方法、 +路由模式和狀態標籤,覆蓋每一個入站請求 —— 包括登入和 REST 資料 API。 + +### 用 OpenTelemetry 匯出 [#export-with-opentelemetry] + +指標通過 **OTLP** 離開執行時。設定 `OS_OBS_EXPORTER=otlp` **和** +`OS_OTLP_ENDPOINT`(例如 `https:///otlp`)。匯出器預設為 `noop`(零 +執行時開銷),所以匯出需要主動開啟 —— 只設置端點而不設定 `OS_OBS_EXPORTER=otlp` +不會發出任何東西,而只設置 `otlp` 卻沒有端點會發出警告並回落到 `noop`。本地除錯時 +使用 `OS_OBS_EXPORTER=console` 或 `json`,用 `OS_OBS_DEPLOYMENT_ENV` 給部署打標籤 +(預設為 `production`)。其餘變數列在 +[環境變數](/docs/reference/environment-variables#observability)中。 + +執行時本身不提供抓取端點。要使用 Prometheus,就把匯出器指向一個 OpenTelemetry +Collector,讓 Collector 把指標暴露給 Prometheus —— 或者把它們傳送到任何接受 OTLP +的後端,例如 Grafana Cloud 或 Datadog。 + +一條入門告警,假設你的管道保留了執行時的指標名稱: + +```yaml +groups: + - name: objectos + rules: + - alert: ObjectOS5xxSpike + expr: | + sum(rate(http_requests_total{status=~"5.."}[5m])) + / sum(rate(http_requests_total[5m])) > 0.02 + for: 5m + annotations: + summary: "ObjectOS 5xx rate above 2% for 5 minutes" +``` + +對 `http_requests_total{status=~"5.."}` 告警,而不是對 `http_request_errors_total`: +那個序列已在 ObjectStack 17.2 中退役,讀數恆為零,看起來就像一個健康的伺服器。 + +冷啟動不在這個列表上。核心只在啟動時構建一次,程序在那之後才報告 +[就緒](/docs/deploy/docker#health-checks) —— 沒有穩態的快取未命中訊號可以告警, +所以改為對就緒狀態告警。 + +認證失敗也不在上面。執行時的指標註冊表覆蓋 HTTP、儲存、快取、後臺作業和包登錄檔; +其中沒有一項會統計被拒絕的憑據,所以沒有可以告警的序列。從 `http_requests_total` +中統計 401/403 也不能替代它:被拒絕的 API 金鑰與一個根本沒有攜帶憑據的請求無法 +區分,而且這兩種狀態碼中佔絕大多數的,是針對已經正常認證的使用者的普通許可權拒絕。 +請改為以 `view_all_audit_log` 持有者的身份,在 Setup 的 `Auth` 審計檢視中審查登入 +活動 —— 平臺管理員預設持有它。沒有它的話,登出以及其會話此後已被移除的登入都不會 +顯示;參見[審計日誌](/docs/operate/audit-logs#what-each-reader-is-served)。 + +## 審計日誌 [#audit-logs] + +啟用審計能力後,ObjectOS 會把審計記錄寫入 `sys_audit_log`。 + +審計日誌用於: + +- 許可權敏感的變更; +- 設定變更; +- 使用者/會話調查; +- 整合活動; +- 訪問被拒絕的分析。 + +對於受監管的環境,請在資料庫或儲存層把審計表設為只追加,並定義保留策略。 + +完整的欄位參考、預設檢視以及同類的診斷日誌,參見 +[審計日誌](/docs/operate/audit-logs)。 + +## Setup 診斷 [#setup-diagnostics] + +Setup 提供以下運維介面: + +- 會話; +- 審計日誌; +- 通知; +- 啟用 webhook 時的 Webhook 投遞記錄; +- 系統和安全概覽儀表盤。 + +這些介面面向運維人員和支援工程師,而不僅僅是開發者。