# 升级与回滚

Raise 系统的升级与回滚采用向后兼容策略：数据库迁移只向前增加数据表或数据列，不提供反向回滚；遇到异常回滚时，仅需换回上一版本的应用镜像或 Helm Chart。在 Docker Compose 环境下，升级流程依次为数据备份、拉取新版本镜像、执行数据库迁移与重启容器；在 Kubernetes Helm 环境下，升级通过 Hook 机制先执行数据迁移与组件引导，随后按零不可用（`maxUnavailable: 0`）策略滚动更新 Pod。

## 谁能用

升级与回滚属于底层系统运维操作，涉及以下角色与权限要求：

| 角色 / 身份 | 权限与访问要求 | 说明 |
|---|---|---|
| 部署运维人员 | 具备宿主机系统权限（`root` 或 `sudo`）或 Kubernetes 集群管理权限 | 负责执行镜像拉取、数据库迁移、服务重启与版本回滚等底层操作。 |
| 数据库 Schema 所有者 | 拥有数据库结构变更权限的连接凭据（`KWORK_MIGRATE_DATABASE_URL`） | 必须使用 schema 所有者凭据执行数据库迁移，严禁将所有者凭据提供给日常业务运行时。 |
| 平台运营人员 | [运营台](/ops/)访问权限 | 升级完成后登录运营台验证服务可用性。运营台未提供升级向导。 |
| 租户管理员与组织成员 | [工作台](/guide/workspace)日常使用权限 | 租户端无升级操作界面。系统升级瞬间，正在使用工作台的成员可能会短暂感知到连接重连。 |

> **注意**
>
> 执行数据库迁移必须使用 schema 所有者专用连接串（`KWORK_MIGRATE_DATABASE_URL`）。运行中的 kwork 主服务仅使用权限受限的 `kwork_app` 账号，无法也不应执行结构变更。

## 怎么打开

系统升级与回滚不提供 Web 图形界面，所有操作均在服务器终端或集群管理终端中执行：

1. **BOM 版本清单文件**：
   查看 `deploy/versions.env`（记录各组件镜像版本与 digest）。
2. **Docker Compose 部署目录**：
   进入服务器的 `deploy/compose` 目录，安装脚本 `install.sh` 支持重复执行（已完成的步骤会自动跳过）。
3. **Helm 升级命令入口**：
   在集群管理终端执行：
   ```bash
   helm upgrade kwork deploy/helm/kwork -n kwork -f my-values.yaml --wait
   ```
4. **离线升级镜像包**：
   离线部署包中同时包含当前新版本与上一版本的 BOM 镜像。
5. **升级验收入口**：
   - 检查容器运行状态：`docker compose ps`
   - 检查工作台连通性：`curl -I https://app.{base}`
   - 检查核心能力接口：`curl -s https://api.{base}/api/capabilities`
   - 访问运营后台：打开并登录 [运营台](/ops/)。

## 界面一览

Raise 系统未提供前端图形化的升级向导，租户端与运营台均无在线升级操作界面。升级过程在终端中运行，当重新执行 `install.sh` 安装脚本时，终端会按执行阶段打印以下步骤标识：

| 终端步骤标识 | 阶段说明 |
|---|---|
| `starting postgres and zitadel` | 检查并拉起基础数据库 PostgreSQL 与身份认证中心 Zitadel 服务。 |
| `database roles` | 初始化并核对数据库角色与运行权限。 |
| `database migrations` | 执行数据库 schema 结构迁移（加表、加列）。 |
| `openbao and object storage` | 初始化或更新 OpenBao 密钥策略与 S3 对象存储配置。 |

## 常用操作

### Docker Compose 常规升级

1. 查阅发布说明与版本差异：
   阅读新版本发布说明，对比 `deploy/versions.env` 的镜像 digest 变动与数据库迁移列表。
2. 备份数据与状态：
   升级前必须执行全量备份（参考 [备份与恢复](/deploy/backup)）。备份 PostgreSQL 数据库，并运行 `backup-state.sh` 脚本备份状态文件。
3. 拉取新版本镜像：
   在终端执行以下命令：
   ```bash
   docker compose pull
   ```
   若为离线部署环境，通过离线包加载新镜像（离线包内同时包含上一版本 BOM 镜像）。
4. 执行数据库迁移：
   在终端中运行迁移容器，传入 schema 所有者数据库连接凭据：
   ```bash
   docker compose run --rm -e KWORK_MIGRATE_DATABASE_URL=postgres://kwork:…@postgres:5432/app?sslmode=disable \
     kwork migrate
   ```
> **破坏性变更处理**
>
>    系统的破坏性结构变更严格分两个版本完成：前一个版本先添加新列或新表，后一个版本再清理废弃的旧列或旧表，确保新旧版本代码兼容。
5. 启动新版本容器：
   执行启动命令拉起新容器：
   ```bash
   docker compose up -d
   ```
6. 验收服务可用性：
   - 执行 `docker compose ps` 确认所有容器处于正常运行状态。
   - 执行 `curl -I https://app.{base}` 确认工作台门户响应正常。
   - 执行 `curl -s https://api.{base}/api/capabilities` 确认开放 API 核心能力接口正常。
   - 登录 [运营台](/ops/)，确认运营后台能够正常登录访问。

### 主机一键升级（Compose）

如果采用主机一键脚本部署：

1. 登录服务器终端，进入 `deploy/compose` 目录。
2. 确认已完成数据库与状态文件的备份（参考 [备份与恢复](/deploy/backup)）。
3. 拉取最新代码并执行一键升级命令：
   ```bash
   git pull && sudo ../setup-host.sh --yes
   ```
   脚本会自动重新构建本地镜像、执行数据库迁移，并沿用原有 `.env` 文件中的全部配置参数滚动重启服务。

### 重跑 install.sh 脚本升级

在 `deploy/compose` 目录下重新运行 `install.sh` 脚本升级系统（脚本已做幂等处理，已完成的步骤自动跳过）：

1. 启动基础设施：必要时启动 PostgreSQL 与 Zitadel 服务（`starting postgres and zitadel`）。
2. 配置角色权限：配置并核对数据库角色（`database roles`）。
3. 执行数据迁移：执行数据库结构迁移（`database migrations`）。
4. 基础设施引导：附加 `--infra` 参数完成 S3 对象存储与 OpenBao 密钥管理系统的初始化引导（`openbao and object storage`）。
5. 身份中心引导：执行 `bootstrap zitadel` 引导（该操作具备幂等性，客户端密钥沿用原有配置，不重复生成）。
6. 启动业务服务：拉起全部服务容器并完成升级。

### 手工升级关键迁移核对

手工升级或跨版本升级时，必须核对以下关键迁移项：

- `audit/0003`：必须由数据库 schema 所有者执行。
- `0025_uninstall_keeps_settings.sql`：升级时必须执行该迁移，若遗漏此迁移，在后续安装应用时会报错 500。
- 审计对象锁：审计对象锁定功能只能在新建存储桶时启用；已有部署若需启用审计锁，应更换为专用存储桶，不要对旧的分段数据进行迁移。

### Helm Chart 升级

在 Kubernetes 集群环境下使用 Helm 进行升级：

1. 核对并编辑 `my-values.yaml` 配置文件，确认镜像版本与参数。
2. 在终端执行 Helm 升级命令：
   ```bash
   helm upgrade kwork deploy/helm/kwork -n kwork -f my-values.yaml --wait
   ```
3. 系统自动按顺序执行 Hook（Hook 执行顺序与全新安装保持一致）：
   - 执行可选数据库角色初始化。
   - 执行 `kwork migrate` 数据库迁移。
   - 并行执行 `bootstrap s3` 与 `bootstrap zitadel` 初始化引导。
4. 客户端密钥沿用：
   - Bootstrap 的执行结果保存在 Secret `<release>-zitadel-bootstrap` 中（该 Secret 在卸载时不会被删除）。
   - 客户端密钥仅在首次创建时返回，后续升级自动沿用（通过 `KWORK_BOOTSTRAP_OUT` 与 `KWORK_ZITADEL_BOOTSTRAP_FILE` 机制），密钥不会输出到 Job 日志中。
5. 滚动更新应用 Pod：
   - 升级采用 `maxUnavailable: 0` 策略进行平滑滚动，确保升级过程中始终有旧版本副本对外提供服务。
   - 当配置或 Secret 发生变动时，Pod 模板的校验和会同步改变，自动触发 Pod 重启。
6. 健康探针与平滑下线：
   - 业务 Pod 在 8009 内部端口提供 `/livez` 存活探针与 `/readyz` 就绪探针。
   - 容器停止时触发 `preStop` 生命周期钩子：先休眠 5 s，再执行 20 s 排空。
   - Pod 配置 `terminationGracePeriodSeconds: 35`，底层集群要求 Kubernetes 版本 ≥ 1.30。

### 集群扩容与连接数规划

升级时若需要增加服务副本数：

1. 修改 `my-values.yaml` 中的副本数配置 `kwork.replicas`。
2. 核对 PostgreSQL 数据库最大连接数，确保满足以下约束公式：
   \[
   \text{副本数} \times (\text{连接池数} \times \text{database.maxConns} + 1) < \text{max\_connections}
   \]
3. 重新执行 `helm upgrade` 命令应用扩容配置。

### 版本回滚

若新版本上线后遇到异常需要紧急回退：

1. **回滚原则**：
   - **镜像回退**：换回上一版本的 BOM 镜像。
   - **数据库迁移不回滚**：迁移仅向前加列或加表，新列对旧版本代码无害。各条迁移是否可回退并未逐个核对，因此发版前必须先做好全量备份。
   - **严禁反向撤销**：切勿使用 `helm rollback` 指望撤销数据库 schema 变动，Hook 不会执行反向迁移。
2. **Compose 镜像回滚**：
   - 方式一：将 `deploy/versions.env` 中的镜像 digest 恢复为上一版本 BOM 的旧 digest，执行 `docker compose up -d`。
   - 方式二：编辑 `deploy/compose/.env` 文件，将 `KWORK_IMAGE` 与 `GATEWAY_IMAGE` 改回上一版本镜像标签，随后执行：
     ```bash
     docker compose up -d kwork gateway
     ```
3. **Helm 镜像回滚**：
   使用旧版 Chart 与上一版本镜像配置，再次执行 `helm upgrade` 命令覆盖发布。
4. **离线包回滚**：
   离线升级包中已包含上一版本的 BOM 镜像，可直接加载旧镜像并重启。

### 升级后常规维护

系统升级完成后，根据变动项完成以下常规维护：

1. **更新 OpenBao 访问策略**：
   重新运行 `install.sh` 脚本会更新 `openbao-kwork.hcl` 策略文件（例如清除凭据操作需要具备 `secret/metadata/kwork/cred/*` 的 delete 权限）。
2. **Langfuse SSO 客户端核对**：
   旧部署首次升级到包含 Langfuse 的版本时，系统 bootstrap 引导过程会自动创建该客户端并仅返回一次密钥。
3. **监控配置生效**：
   若修改了 `otelcol.yaml` 或 `prometheus.yml`，重新运行 `install.sh` 会自动重启采集器与 Prometheus；若为手动修改，需在终端执行：
   ```bash
   docker compose restart otel-collector prometheus
   ```
4. **PostgreSQL 大版本升级处理**：
   若升级涉及 PostgreSQL 大版本切换，升级完成后必须立刻执行全量备份，并在配置中更换 `BACKUP_S3_PREFIX`（参考 [备份与恢复](/deploy/backup)）。

## 状态说明

系统升级过程中的阶段流转与状态说明如下：

### Docker Compose 升级流程
`备份数据与状态 → 拉取新镜像 (docker compose pull) → 数据库迁移 (kwork migrate) → 启动容器 (docker compose up -d) → 服务验收`

### Helm 升级流程
`可选数据库角色 Hook → 数据库迁移 Hook (kwork migrate) → 组件引导 Hook (S3 / Zitadel) → 滚动更新 Pod (maxUnavailable: 0)`

### 探针与生命周期状态

| 状态 / 端点 | 端口与配置 | 说明 |
|---|---|---|
| 存活探针（livez） | 8009 端口 `/livez` | 检查应用进程是否处于存活状态。 |
| 就绪探针（readyz） | 8009 端口 `/readyz` | 检查应用是否已完成初始化并可接收流量。 |
| 平滑下线排空 | `preStop` 钩子 | 触发停止后先休眠 5 s，再执行 20 s 排空；配合 `KWORK_AGENT_DRAIN_S` 等待任务处理完毕。 |

## 让助手帮你做

系统升级与版本回滚属于底层服务器与容器集群运维工作，助手未接入任何宿主机或集群的管理指令，无法通过助手在工作台中执行升级、迁移或回退操作。

你可以在日常使用中向助手咨询升级规则与回滚流程，例如：
> 升级 Raise 前需要备份哪些数据与配置文件？
> 为什么数据库迁移不支持反向回滚？
> 在 Docker Compose 环境下如何将镜像回退到上一版本？
> Helm 升级时如果有 Hook 执行失败，当前正在运行的服务会受影响吗？
> 升级服务时对正在使用工作台的成员会有什么影响？

## 设置

系统没有单独的升级开关图形界面。升级与停机排空相关的系统环境变量如下：

| 环境变量 / 配置项 | 必填 | 默认值 | 说明 |
|---|---|---|---|
| `KWORK_MIGRATE_DATABASE_URL` | 执行迁移时必填 | 空 | 业务数据库迁移连接地址。必须使用具备 schema 所有者权限的连接凭据，严禁提供给普通运行时使用。 |
| `KWORK_MIGRATE_AUDIT_DATABASE_URL` | 按需配置 | 空 | 审计数据库迁移连接地址。必须由 schema 所有者凭据执行。 |
| `KWORK_AGENT_DRAIN_S` | 否 | `60` | 服务下线时 Agent 任务的排空等待时长（秒）。Compose 环境中容器停止宽限期默认为 90 s。 |

## 注意事项

- **所有者凭据权限隔离**：数据库迁移必须使用 schema 所有者专用连接串（`KWORK_MIGRATE_DATABASE_URL`），严禁将其配置给日常业务运行时。运行中的 kwork 主服务仅使用权限受限的 `kwork_app` 账号。
- **数据库迁移单向不可回滚**：迁移脚本仅向前增加数据表和数据列，**不提供回滚**。新数据列对旧版本代码无害。发版前必须先完成全量数据备份。
- **避免短时强行停机**：Agent 任务排空等待时间由 `KWORK_AGENT_DRAIN_S` 控制（默认 60 s），Compose 停止宽限期默认为 90 s。切勿在终端执行 `docker stop -t 10` 强行短时停止容器，否则会导致排空不彻底。
- **升级对在线业务的影响**：
  - 服务切换瞬间，[工作台](/guide/workspace)可能会短暂提示「连接中断，正在重新连接…」。
  - [知识库](/apps/wiki)同步可能短暂出现 1012 状态。
  - 助手正在写入的单步操作会尽量在排空时间内写完；若超时未完成，将在心跳恢复后重新执行。
- **商业许可生效差异**：修改许可证（License）时，席位配额、应用列表与到期时间支持热更新；但许可证中的「模块」与「功能」仅在服务启动时读取，修改后必须重启服务才能生效。
- **多副本启动重试保护**：多副本并发启动时，内置应用发布遇到冲突会自动重试。
- **Kubernetes 部署配置要求**：
  - 必须设置 `enableServiceLinks: false`，否则名为 `kwork` 的 Service 会自动注入 `KWORK_PORT` 等环境变量，与系统配置发生冲突。
  - 网关证书 PVC 标记了 `helm.sh/resource-policy: keep`，卸载 Helm release 时不会被删除。
  - Helm 升级过程中任一 Hook 失败，整次 release 升级即宣告失败，正在运行的旧版本服务保持不变。

## 目前不支持

- **无图形化升级向导**：运营后台未提供升级向导或在线升级操作按钮，所有升级均通过终端命令行或 Helm 执行。
- **无自动蓝绿与金丝雀发布**：自动蓝绿发布与金丝雀发布未作为产品功能提供。
- **Helm 不支持托管 ISV 运行时**：Helm 部署形态不支持托管 ISV 运行时（目前仅提供 Docker 适配器）。
- **Helm 部分场景未经验证**：在 Helm 部署形态下，Agent 走完登录流程、真实负载均衡与 ACME 证书集成、多节点排空、网关多副本运行，文档明确标明未在真实集群上进行验证。

## 常见问题

### 升级遇到问题需要回退时，数据库迁移可以一起回滚吗？
不能。Raise 的数据库迁移只向前增加数据表或数据列，不提供反向回滚；新增加的列对旧版本代码无害。回滚时仅需换回上一版本 BOM 镜像或旧版 Helm Chart。在发版与升级前，必须先完成全量数据备份。

### 执行 Helm 升级时，如果某个 Hook 失败会发生什么？
Helm 任一 Hook 失败时，整次 release 升级宣告失败，正在运行的旧版本 Pod 保持不变，不会中断当前业务。注意切勿使用 `helm rollback` 指望撤销数据库结构变更，Hook 不会执行反向迁移。

### 升级服务切换时，终端用户端会有什么感知？
在服务切换瞬间，[工作台](/guide/workspace)可能短暂显示「连接中断，正在重新连接…」；[知识库](/apps/wiki)协同可能短暂出现 1012 状态；助手正在处理的单步操作会尽量在排空期内完成，超时未完成的任务会在心跳恢复后重跑。

### 为什么执行数据库迁移不能使用运行时的数据库连接？
数据库迁移需要执行建表、加列等结构定义操作，必须使用具备 schema 所有者权限的连接凭据（`KWORK_MIGRATE_DATABASE_URL`）。为了保障生产环境最小权限安全，运行中的 kwork 主服务仅拥有权限受限的 `kwork_app` 账号，不能持有所有者凭据。

### 升级后修改了许可证（License），为什么新增的功能没有出现？
许可证中的席位配额、应用授权清单与到期时间支持热更新，但其中启用的「模块」与「功能」仅在服务启动时读取。若修改了许可证中的模块或功能配置，必须重启服务方可生效。
