521 lines
26 KiB
Markdown
521 lines
26 KiB
Markdown
# PostgreSQL Tenant Operator 系统规格说明书
|
||
|
||
| 项目 | 内容 |
|
||
| --- | --- |
|
||
| 状态 | Approved |
|
||
| 目标 API | `database.ddupan.top/v1alpha1` |
|
||
| 最后更新 | 2026-09-10 |
|
||
| 批准日期 | 2026-09-10 |
|
||
| 规范范围 | 首次注册外部 PostgreSQL 实例并创建一个应用租户 |
|
||
|
||
本文档定义系统对用户和外部依赖呈现的行为,是 API、测试和实现共同遵守的合同。
|
||
实现若需要改变本文合同,必须先修改规格并重新获得批准。
|
||
|
||
文中的“必须”“禁止”“应当”“可以”分别对应强制要求、强制限制、推荐行为和可选
|
||
行为。
|
||
|
||
## 1. 背景
|
||
|
||
homelab 中的大部分应用共享一个运行在独立 VM 上的 PostgreSQL DBMS。应用需要各自
|
||
独立的 database、作为 owner 的 login role 和密码,但不需要独立 PostgreSQL 实例。
|
||
目前这些资源依靠人工 SQL 和人工 Secret 管理,难以重复、审计和检测漂移。
|
||
|
||
本系统使用 Kubernetes CRD 作为声明式 API,持续协调外部 PostgreSQL 与 OpenBao:
|
||
|
||
```text
|
||
PostgreSQLInstance / PostgreSQLTenant
|
||
|
|
||
v
|
||
postgresql-tenant-operator
|
||
| |
|
||
v v
|
||
PostgreSQL catalog OpenBao KV v2
|
||
```
|
||
|
||
## 2. 目标
|
||
|
||
v1alpha1 必须实现以下目标:
|
||
|
||
1. 注册一个已经存在的外部 PostgreSQL 实例并报告连接状态。
|
||
2. 为一个应用租户创建独立 database 和一个同时作为 database owner 的 login role。
|
||
3. 根据实例 allowlist 安装租户申请的 PostgreSQL extension。
|
||
4. 首次生成高强度长期密码,并只把凭据明文写入 OpenBao KV v2。
|
||
5. 为 Kubernetes 应用创建 ExternalSecret,由 ESO 将凭据投射到同 namespace Secret。
|
||
6. 同时输出 OpenBao API URL,使 Kubernetes 外的应用可以直接读取凭据。
|
||
7. 同时输出 PostgreSQL DNS hostname 和 IP address,不假定所有消费者都能使用集群内
|
||
DNS。
|
||
8. 持续检测并修正由本系统管理的非破坏性漂移。
|
||
9. 通过 Kubernetes Condition 报告进度、成功和可操作的失败原因。
|
||
10. 重复 reconcile、controller 重启及外部依赖暂时失败不得重复创建或破坏资源。
|
||
11. 删除 Tenant CR 时默认保留外部资源;显式选择 `Delete` 时提供完整清理路径。
|
||
|
||
## 3. 非目标
|
||
|
||
v1alpha1 不负责:
|
||
|
||
- 创建、升级、备份或高可用运行 PostgreSQL DBMS/VM;
|
||
- 创建或运维 OpenBao;
|
||
- 直接写入包含凭据明文的 Kubernetes Secret;Secret 必须由 ESO 投射;
|
||
- 动态凭据、定时或自动密码轮换;
|
||
- Web UI、独立 REST API 或 Backstage 插件;
|
||
- 跨实例迁移 database;
|
||
- schema/table 级别租户、多 login role 或跨租户 grant;
|
||
- 删除不属于本系统管理的 database、role、extension 或 OpenBao Secret;
|
||
- 接管不是由本系统创建的外部资源;
|
||
- 提供生产环境 SLA。
|
||
|
||
## 4. 参与者与事实来源
|
||
|
||
| 对象 | 事实来源 | 说明 |
|
||
| --- | --- | --- |
|
||
| 期望状态 | Kubernetes CR `spec` | 用户声明的合同 |
|
||
| 最近观察结果与当前阶段 | Kubernetes CR `status` | 可以丢失并重建,不是外部事实来源 |
|
||
| database/role/grant/extension | PostgreSQL catalog | 每轮 reconcile 必须重新读取 |
|
||
| 受管资源所有权与保留标记 | PostgreSQL controller registry | 与受管 DBMS 一起备份和恢复 |
|
||
| controller 工作流阶段 | Kubernetes CR `status.phase` | 状态机 checkpoint;可由外部事实保守重建 |
|
||
| 应用凭据 | OpenBao KV v2 | Kubernetes API 中不得出现明文 |
|
||
| Kubernetes 凭据投射 | External Secrets Operator | ExternalSecret 由本 controller 管理 |
|
||
| PostgreSQL 管理凭据 | OpenBao KV v2 | 由 `PostgreSQLInstance` 引用 |
|
||
|
||
平台管理员管理 `PostgreSQLInstance`、controller 部署配置、OpenBao policy 和
|
||
PostgreSQL 管理 role。应用或 GitOps 流程在获得 namespace RBAC 后管理
|
||
`PostgreSQLTenant`。
|
||
|
||
## 5. 资源模型
|
||
|
||
### 5.1 PostgreSQLInstance
|
||
|
||
`PostgreSQLInstance` 是 cluster-scoped 资源,表示一个已经存在、可由 controller
|
||
管理的 PostgreSQL server。
|
||
|
||
它必须声明:
|
||
|
||
- PostgreSQL host、port 和管理连接使用的 database;
|
||
- PostgreSQL host address,供无法解析 DNS 的消费者使用;
|
||
- TLS mode;
|
||
- PostgreSQL 管理凭据在 OpenBao 中的位置和字段名;
|
||
- 租户允许申请的 extension 集合。
|
||
|
||
实例 Ready 不代表 PostgreSQL 数据有备份或高可用,只表示 controller 当前可以安全
|
||
建立管理连接、读取 server metadata、访问 controller registry 并使用所需管理能力。
|
||
|
||
### 5.2 PostgreSQLTenant
|
||
|
||
`PostgreSQLTenant` 是 namespaced 资源。v1alpha1 中,一个 Tenant 精确对应:
|
||
|
||
- 一个 `PostgreSQLInstance`;
|
||
- 一个 database;
|
||
- 一个同时作为 database owner、供应用使用的 `LOGIN` role;
|
||
- 零个或多个 extension;
|
||
- 一个 OpenBao KV v2 凭据位置;
|
||
- 一个同 namespace ExternalSecret 及其目标 Kubernetes Secret。
|
||
|
||
Tenant 的 namespace 用于 Kubernetes RBAC 和身份识别,不代表 PostgreSQL schema。
|
||
同一 Instance 中的 database 和 role 名称全局唯一。
|
||
|
||
## 6. 标识与默认值
|
||
|
||
以下是 v1alpha1 的标识合同:
|
||
|
||
| 字段 | 默认值 | 约束 |
|
||
| --- | --- | --- |
|
||
| Instance port | `5432` | 1–65535 |
|
||
| Instance host address | 无 | 必须是合法 IPv4 或 IPv6 address |
|
||
| 管理 database | `postgres` | 合法 PostgreSQL identifier |
|
||
| TLS mode | `verify-full` | 禁止隐式降级 |
|
||
| Tenant database | `metadata.name` | 同一 Instance 全局唯一 |
|
||
| login role | `metadata.name` | 同一 Instance 全局唯一 |
|
||
| deletion policy | `Retain` | `Retain` 或 `Delete` |
|
||
|
||
Tenant 的 `spec.instanceRef` 与 `metadata.name` 长度合计不得超过 241 个字符,确保
|
||
派生的 ExternalSecret/Secret 默认名称
|
||
`<instanceRef>-<metadata.name>-postgresql` 不超过 Kubernetes 253 字符限制。
|
||
|
||
固定默认值由 CRD defaulting 写入。依赖 `metadata.name` 或 `instanceRef` 的 database、
|
||
login role、ExternalSecret/Secret 名称属于 controller 语义默认值:省略字段不会被 admission
|
||
回写,controller 必须始终计算同一个 effective value,并通过 status 的 database、
|
||
loginRole、credential reference 以及实际资源展示。
|
||
v1alpha1 不为此引入 mutating webhook。
|
||
|
||
database 和 role 名称必须作为 PostgreSQL identifier 参数安全引用,禁止通过字符串
|
||
拼接执行。名称校验必须拒绝空字符串、NUL 和超过 PostgreSQL identifier 长度限制的
|
||
值,并统一限制为小写字母、数字和下划线。
|
||
|
||
Tenant 首次成功后,`instanceRef`、database、login role 和凭据位置必须
|
||
不可变。修改这些字段不是 rename 或 migration,API 必须拒绝或报告明确的
|
||
`ImmutableField`。
|
||
|
||
## 7. PostgreSQL 权限合同
|
||
|
||
建议的 v1alpha1 权限模型如下:
|
||
|
||
1. database 必须由 login role 拥有。
|
||
2. login role 必须是 `LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION`。
|
||
3. 必须撤销 `PUBLIC` 对租户 database 的连接权限,再显式允许 login role 连接。
|
||
4. controller 不得修改其他 database 或无关 role 的权限。
|
||
5. controller 只保证请求的 extension 存在;移除 extension 不得自动执行
|
||
`DROP EXTENSION`。
|
||
|
||
这意味着应用可以在自己的 database 内执行 schema migration,但不能创建其他
|
||
database、role 或访问其他租户。v1alpha1 不创建只有形式意义、却未隔离运行时权限的
|
||
额外 `NOLOGIN` owner。若以后应用能分别使用 migration 和 runtime 凭据,再通过新的
|
||
权限 profile 引入 owner/migrator/runtime 角色模型。
|
||
|
||
## 8. OpenBao 凭据合同
|
||
|
||
### 8.1 Controller 自身认证
|
||
|
||
controller 必须使用 Kubernetes auth 登录 OpenBao。controller 使用的 OpenBao API
|
||
address、提供给消费者的 OpenBao API address、auth mount、auth role 和 KV v2 mount
|
||
属于部署配置,不属于任何 CR。两个 API address 可以相同;若 controller 使用集群内
|
||
地址而外部消费者不能解析,则必须单独配置 consumer address。生产部署的 KV mount
|
||
默认为 `kv`;开发环境可以配置为 OpenBao dev server 默认的 `secret`。长期 OpenBao
|
||
Token 禁止写入 Deployment、CR 或镜像。
|
||
|
||
### 8.2 管理凭据
|
||
|
||
`PostgreSQLInstance` 只引用 PostgreSQL 管理用户名和密码所在的 OpenBao KV v2
|
||
mount-relative path。controller 对该路径只需要读取权限。
|
||
|
||
### 8.3 租户凭据
|
||
|
||
Tenant 不声明凭据 path。controller 根据部署级 KV mount、base path 和 Tenant 的
|
||
namespace/name 推导唯一的 mount-relative path。base path 来自 controller 启动参数
|
||
`--openbao-tenant-base-path`,默认 `postgresql-tenants`。最终路径为
|
||
`<base-path>/<namespace>/<metadata.name>`。推导结果禁止以 `/` 开头,禁止包含空路径段、
|
||
`.`、`..`,也禁止把 KV v2 HTTP API 的 `data` 或 `metadata` 层编码进路径。
|
||
|
||
新 Tenant 的凭据建立顺序必须可从任意中断点恢复:
|
||
|
||
1. 验证 Instance、名称、extension 和目标 OpenBao 路径;
|
||
2. 确认目标 database、role 和 OpenBao 记录不存在,或能够验证为同一 Tenant
|
||
已创建的部分状态;
|
||
3. 生成密码;
|
||
4. 先创建带 controller 所有权 metadata 的 OpenBao KV v2 记录;
|
||
5. 从 OpenBao 重新读取凭据;
|
||
6. 使用该凭据创建作为 database owner 的 login role 和其他 PostgreSQL 资源;
|
||
7. 用 login role 实际连接目标 database;
|
||
8. 全部验证成功后将 Tenant 标记 Ready。
|
||
|
||
若第 4 步成功、后续 PostgreSQL 操作失败,下一轮必须读取同一份 OpenBao 凭据继续,
|
||
不得生成第二个密码。若 PostgreSQL 先存在而 OpenBao 记录不存在,controller 必须报告
|
||
Conflict,不得擅自重置已有 role 密码。
|
||
|
||
controller 必须在 PostgreSQL 管理 database 的专用 registry schema 中持久保存可验证的
|
||
Instance UID、Tenant UID 与 namespace/name 关联,不能只依赖会丢失的 CR status 判断
|
||
资源所有权。registry 必须可回读且不得改变数据库授权语义;database 或 role COMMENT
|
||
不能作为权威所有权记录。
|
||
|
||
默认写入字段固定为:
|
||
|
||
```text
|
||
username
|
||
password
|
||
database
|
||
host
|
||
hostaddr
|
||
port
|
||
sslmode
|
||
```
|
||
|
||
这些字段是 controller 的规范化输出合同。controller 不生成包含密码的 URI、JDBC URL
|
||
或应用专用键名。应用通过 ExternalSecret template、Helm values 或自身配置把原子字段
|
||
映射为 `DATABASE_URL`、独立环境变量或配置文件;因此 URI escaping 和应用特有格式也
|
||
由消费方负责。`host` 是 DNS 名称,`hostaddr` 是可直接连接的 IP;消费者自行选择其
|
||
支持且可达的连接目标。PostgreSQL server 证书必须同时包含与 `host` 匹配的 DNS SAN
|
||
和与 `hostaddr` 匹配的 IP SAN,使两种目标都能在 `verify-full` 下独立完成身份验证。
|
||
|
||
### 8.4 凭据输出与 ExternalSecret
|
||
|
||
controller 必须根据部署级 base path 推导 Tenant 的 KV path,Tenant 不能选择 mount 或
|
||
任意远端路径。ExternalSecret 固定命名为
|
||
`<instanceRef>-<metadata.name>-postgresql`。Tenant 可以通过
|
||
`spec.credential.secretName` 指定目标 Kubernetes Secret 名称;省略时使用同一默认名。
|
||
自定义名称只需是合法 Kubernetes Secret 名称,不限制命名内容;两者均与 Tenant 位于
|
||
同一 namespace。
|
||
|
||
controller 必须创建同 namespace ExternalSecret,从固定的 ClusterSecretStore 读取七个
|
||
原子字段。ExternalSecret 及目标 Secret 的名称通过 Tenant status 暴露。controller
|
||
不得直接读取 OpenBao 密码后写入 Kubernetes Secret。
|
||
|
||
Tenant status 还必须提供完整、可由外部消费者使用的 OpenBao KV v2 API URL。URL 可以
|
||
包含 consumer API address、mount 和 secret path,但不得包含 Token、密码或其他认证
|
||
信息。默认 `kubectl get` 表格显示目标 Secret 名称;完整 OpenBao URL 通过
|
||
`kubectl get postgresqltenant <name> -o yaml` 获取,避免表格列过长。
|
||
|
||
OpenBao metadata 必须能够标识 Tenant UID、namespace/name 和 Instance,使 controller
|
||
区分自己的残留记录与外部记录。任何凭据值都不得进入日志、Event、Condition、metric
|
||
label、trace、CR spec/status 或测试快照。
|
||
|
||
## 9. Reconcile 行为
|
||
|
||
系统采用最终一致性模型。Kubernetes、PostgreSQL、OpenBao 和 ESO 可以短暂处于不同
|
||
阶段;controller 不尝试实现跨系统事务,而是以 Kubernetes CR `status.phase` 作为
|
||
工作流 checkpoint,通过幂等外部操作和每轮回读验证最终收敛。
|
||
|
||
两个 CR 的状态机权威记录都在 `status.phase`。controller 根据 phase 选择下一项候选
|
||
动作,但 phase 不能替代外部状态检查:执行前后仍须回读 PostgreSQL catalog、registry、
|
||
OpenBao 和 Kubernetes/ESO。外部写入成功但 status 更新失败时,下一轮必须识别已完成
|
||
事实并推进 phase,不得重复生成密码或报告虚假冲突。
|
||
|
||
status 丢失时,controller 必须从 registry 的所有权记录和各外部系统实际状态保守重建
|
||
phase。若 status 被伪造或领先于实际状态,controller 必须纠正到安全阶段并补齐资源,
|
||
不能跳过验证。registry 不保存或驱动协调 phase。
|
||
|
||
Instance phase 按当前 generation 表示连接与初始化进度:
|
||
|
||
```text
|
||
Pending -> Validating -> InitializingRegistry -> Ready
|
||
(any phase) --------------------------------> Deleting
|
||
```
|
||
|
||
spec generation 改变后可以从 `Ready` 回到 `Validating`。Tenant phase 如下:
|
||
|
||
```text
|
||
Pending -> Planned -> CredentialCreated -> RoleCreated -> DatabaseCreated
|
||
-> ExternalSecretCreated -> CredentialProjected -> Ready -> Deleting
|
||
```
|
||
|
||
失败不增加 `Failed` phase;phase 保留在无法推进的步骤,由 `Ready=False` 的 Reason 和
|
||
message 表达 `Conflict`、认证失败或依赖不可用。Retain 删除完成后 CR 已不存在,因此
|
||
没有持久的 `Retained` phase。
|
||
|
||
每轮 Tenant reconcile 必须按以下逻辑执行:
|
||
|
||
```text
|
||
读取 Tenant
|
||
-> 读取 Instance
|
||
-> 校验不可变字段与输入
|
||
-> 检查 Instance Ready
|
||
-> 读取 OpenBao 与 PostgreSQL 实际状态
|
||
-> 检测冲突或部分完成状态
|
||
-> 执行非破坏性补齐
|
||
-> 使用应用凭据验证登录
|
||
-> 创建并验证 ExternalSecret/Secret 投射
|
||
-> 回读实际状态
|
||
-> 更新 status
|
||
```
|
||
|
||
要求:
|
||
|
||
- 所有步骤必须幂等;
|
||
- 每个外部写入前必须先在 CR status 持久化足够的操作意图,写入后必须回读并推进
|
||
`status.phase`;
|
||
- 暂时性网络、锁和依赖错误必须重试;
|
||
- 输入错误、资源冲突和禁止操作不得忙循环重试,只在 generation 或依赖状态变化后
|
||
重试;
|
||
- 未知外部资源不得被修改、接管或删除;
|
||
- 用户从 `spec.extensions` 移除 extension 时不得执行卸载,必须报告该字段在 v1alpha1
|
||
中只允许追加;
|
||
- controller 重启不得影响已经签发的应用密码;
|
||
- `status` 丢失后必须可以从 registry、PostgreSQL、OpenBao 和 Kubernetes/ESO 重建。
|
||
|
||
## 10. Condition 合同
|
||
|
||
两个资源都必须提供唯一的 `Ready` Condition。可以增加辅助 Condition,但调用方只需
|
||
依赖 `Ready`。
|
||
|
||
| 状态 | 含义 |
|
||
| --- | --- |
|
||
| `Ready=Unknown` | 正在首次观察或 reconcile,尚无结论 |
|
||
| `Ready=False` | 当前 generation 未达到合同要求 |
|
||
| `Ready=True` | 当前 generation 已回读验证成功 |
|
||
|
||
Condition 必须带正确的 `observedGeneration`。资源自身的
|
||
`status.observedGeneration` 只在当前 generation 完成一次有结论的 reconcile 后更新。
|
||
|
||
最低 Reason 集合:
|
||
|
||
| Reason | 适用资源 | 含义 |
|
||
| --- | --- | --- |
|
||
| `Reconciling` | 两者 | 尚在处理 |
|
||
| `Ready` | 两者 | 当前 generation 已验证 |
|
||
| `InvalidSpec` | 两者 | 输入不满足规格 |
|
||
| `DependencyUnavailable` | 两者 | PostgreSQL 或 OpenBao 暂时不可用 |
|
||
| `AuthenticationFailed` | Instance | 管理凭据或 TLS 验证失败 |
|
||
| `InsufficientPrivileges` | Instance | 管理 role 缺少必要权限 |
|
||
| `InstanceNotReady` | Tenant | 引用的 Instance 未 Ready |
|
||
| `Conflict` | Tenant | 目标名称或 OpenBao 路径已被其他主体占用 |
|
||
| `ProvisioningFailed` | Tenant | 可重试的创建/验证失败 |
|
||
| `CredentialProjectionFailed` | Tenant | ESO 或目标 Secret 未达到期望状态 |
|
||
|
||
Condition message 必须适合人类排障,但禁止包含连接串密码、Token 或完整 Secret 数据。
|
||
|
||
## 11. 删除与保留
|
||
|
||
### 11.1 Retain
|
||
|
||
`Retain` 是默认策略:
|
||
|
||
- 删除 Tenant CR 不得删除 database、role、extension 或 OpenBao 记录;
|
||
- controller 不得因外部依赖不可用而永久阻止 Retain CR 删除;
|
||
- 保留资源必须继续携带原 Tenant UID 和 namespace/name 的所有权记录,但在 CR 删除后
|
||
明确处于 unmanaged 状态;
|
||
- 重新创建同名 Tenant 会产生新的 UID,必须因已有资源不属于新 UID 而报告 Conflict;
|
||
- v1alpha1 不提供重新关联、import 或 adoption;恢复管理必须使用第 12 节的迁移流程,
|
||
或等待后续版本定义显式纳管协议。
|
||
|
||
### 11.2 Delete
|
||
|
||
用户在创建 Tenant 时显式设置 `deletionPolicy: Delete`,表示删除 CR 时授权永久清理
|
||
该 Tenant 的外部资源。controller 必须使用 finalizer,并按以下顺序处理:
|
||
|
||
1. 再次验证 database、role 和 OpenBao 记录都属于当前 Tenant UID;
|
||
2. 删除 ExternalSecret,并确认目标 Kubernetes Secret 已删除;
|
||
3. 禁止该 login role 建立新连接;
|
||
4. 终止该 database 的现有连接;
|
||
5. 删除 database,database 内 extension 随之删除;
|
||
6. 删除 login role;
|
||
7. 删除 OpenBao KV 记录及其可恢复版本;
|
||
8. 回读确认外部资源均不存在;
|
||
9. 删除 controller registry 记录;
|
||
10. 移除 finalizer,允许 Kubernetes 删除 CR。
|
||
|
||
任一步失败都必须保持 finalizer 并从安全检查开始重试。controller 禁止使用
|
||
`CASCADE` 删除无法证明属于该 Tenant 的依赖对象。若 Instance 或 OpenBao 永久丢失,
|
||
管理员可以在核实外部状态后手工移除 finalizer;该逃生操作必须在运维 runbook 中明确
|
||
标记为可能遗留资源。
|
||
|
||
v1alpha1 不自动检查备份,也不承诺恢复被 `Delete` 删除的数据。显式选择 Delete 的
|
||
用户承担数据销毁语义;默认 Retain 用于避免普通误删。
|
||
|
||
## 12. 现有环境迁移
|
||
|
||
v1alpha1 不接管现有 database 或 role,但必须提供可重复、可回滚的迁移 runbook。对每
|
||
个现有应用租户,推荐的停机迁移顺序是:
|
||
|
||
1. 盘点 database、role、owner、grant 和 extension,并完成可恢复备份;
|
||
2. 创建逻辑备份,必须使用可映射到新 owner 的格式,避免恢复旧 role ownership;
|
||
3. 停止应用写入并确认没有活动写事务;
|
||
4. 完成最终逻辑备份;
|
||
5. 将旧 database 和 role 重命名为带迁移时间戳的保留名称,释放最终名称;
|
||
6. 创建 `PostgreSQLTenant`,由 controller 创建最终 database、role 和 OpenBao 凭据;
|
||
7. 等待 Tenant Ready;
|
||
8. 以新 owner 恢复逻辑备份,并验证 row count、schema、extension 和应用权限;
|
||
9. 让 ESO 投射新凭据,重启或重新部署应用;
|
||
10. 验证应用读写后结束维护窗口;
|
||
11. 保留旧 database、role 和备份直到回滚窗口结束,再由管理员手工清理。
|
||
|
||
回滚时停止新应用写入、恢复原名称或连接配置,并重新使用旧凭据。迁移工具不得把旧
|
||
密码、管理凭据或 dump 文件提交到 Git。真实命令、锁定方式和各现有应用验证项见
|
||
[`migration.md`](migration.md),并必须在实现首个可用版本前通过临时 PostgreSQL 实例
|
||
演练。
|
||
|
||
## 13. 安全要求
|
||
|
||
1. 所有 PostgreSQL 与 OpenBao 网络访问必须支持超时和 context cancellation。
|
||
2. homelab 部署必须通过 Deployment 挂载的共享 CA bundle 验证 TLS server identity;
|
||
该 bundle 的信任根来自 OpenBao PKI,但不得包含 CA 私钥。Instance 默认使用
|
||
`verify-full`,其 host 必须与服务器证书名称匹配。开发环境可以显式使用 `disable`
|
||
明文连接。
|
||
3. PostgreSQL 管理 role 应使用满足本规格的最小权限,不应使用 PostgreSQL
|
||
superuser;若 extension 安装需要额外权限,必须单独记录例外。
|
||
4. OpenBao policy 必须限制为:读取已登记的管理凭据范围,以及创建/读取本 controller
|
||
管理的租户 KV 范围。
|
||
5. namespace 用户不得修改 cluster-scoped Instance。
|
||
6. 所有 identifier、extension name 和引用字段必须在发起外部调用前校验。
|
||
7. controller 不得通过 shell 或 `psql` 子进程执行用户输入。
|
||
8. 错误包装、结构化日志和 tracing 必须经过 Secret 泄露测试。
|
||
|
||
详细威胁模型和部署 policy 见 [`security.md`](security.md)。
|
||
|
||
## 14. 可观测性要求
|
||
|
||
v1alpha1 至少必须提供:
|
||
|
||
- Kubernetes Events:开始 provisioning、成功及需要人工处理的失败;
|
||
- 结构化日志:resource namespace/name、Instance、generation、阶段和错误类别;
|
||
- controller-runtime 默认 reconcile metrics;
|
||
- 不包含 database、role、OpenBao path 等无界用户输入的低基数失败分类 metric。
|
||
|
||
日志和 metrics 的存在不能代替 Condition;Condition 是 API 使用者判断状态的主要方式。
|
||
|
||
## 15. 验收标准
|
||
|
||
实现 v1alpha1 第一条完整纵向切片前,测试必须覆盖:
|
||
|
||
1. 有效 Instance 可以建立 TLS 管理连接并变为 Ready。
|
||
2. PostgreSQL 或 OpenBao 暂时不可用时 Ready=False,恢复后自动变为 Ready。
|
||
3. 有效 Tenant 创建 database、作为 owner 的 login、grant、extension 和 OpenBao
|
||
记录。
|
||
4. 应用凭据可以实际连接且不能创建其他 database/role。
|
||
5. 相同 generation 重复 reconcile 不改变密码、不重复创建资源。
|
||
6. controller 在每个外部写入步骤后中断,重启后都能继续并得到相同最终状态。
|
||
7. 预先存在且不属于当前 Tenant UID 的 database、role 或 OpenBao path 导致
|
||
Conflict,且不修改已有资源。
|
||
8. 未在 allowlist 的 extension 在任何外部写入前被拒绝。
|
||
9. status 被清空后可以从两个外部事实来源重建。
|
||
10. 删除 Retain Tenant 后外部资源仍存在且不再受管;重新创建同名 Tenant 报告
|
||
Conflict。
|
||
11. 日志、Event、Condition、metric 和 CR 中不存在生成的密码或管理凭据。
|
||
12. 两个 namespace 对同一 Instance 申请相同名称时,只有第一个成功,第二个报告
|
||
Conflict。
|
||
13. 删除 Delete Tenant 时,任一步骤失败都可重试,且最终删除 database、login role、
|
||
OpenBao KV 历史和 finalizer。
|
||
14. 使用迁移 runbook 可以把一个现有 database 转移到新建的受管 database,并在回滚
|
||
窗口内恢复旧服务。
|
||
15. Tenant 只有在 ExternalSecret Ready、目标 Secret 存在且应用凭据实际可登录后才
|
||
Ready。
|
||
16. Tenant status 同时提供 Kubernetes Secret reference 和不含认证信息的 OpenBao API
|
||
URL。
|
||
17. DNS 不可用时,使用输出的 `hostaddr` 可以连接 PostgreSQL;server 证书同时覆盖
|
||
`host` 的 DNS SAN 和 `hostaddr` 的 IP SAN,两种连接目标均可通过 `verify-full`。
|
||
18. 两个 CR 的 `status.phase` 都能反映当前协调步骤;清空 status 后可以从外部事实重建,
|
||
且伪造或过期 phase 不会使 controller 跳过验证或外部操作。
|
||
|
||
单元测试验证纯决策逻辑,adapter 集成测试使用 Docker PostgreSQL/OpenBao,controller
|
||
集成测试使用 envtest,完整网络路径使用 Kind E2E。
|
||
|
||
## 16. 已确认决策
|
||
|
||
- v1alpha1 使用一个同时作为 database owner 的 login role,不创建额外 NOLOGIN owner。
|
||
- v1alpha1 不接管任意现有资源,但必须提供并演练 dump/restore 迁移路径。
|
||
- v1alpha1 同时实现默认 `Retain` 和显式 `Delete`;Delete 必须有 finalizer、所有权验证
|
||
和完整清理路径。
|
||
- OpenBao KV v2 mount 和 base path 是 controller 部署配置,mount 默认 `kv`,base path
|
||
由 `--openbao-tenant-base-path` 配置并默认 `postgresql-tenants`;Tenant 不能选择 mount
|
||
或任意远端 path,controller 根据 namespace/name 推导记录路径。
|
||
- 租户 KV 记录固定写入 `username/password/database/host/hostaddr/port/sslmode` 七个
|
||
原子字段;
|
||
controller 不生成连接 URI,应用负责映射和拼装自身配置。
|
||
- PostgreSQL TLS 使用 controller Deployment 挂载的共享 CA bundle。OpenBao PKI 是
|
||
CA 权威并继续签发、续期 PostgreSQL server 证书;controller 只消费公开 trust
|
||
bundle,不接触 CA 私钥。bundle 可以由 ConfigMap 或现有证书同步机制投射,不允许
|
||
Tenant 或 Instance 选择其他 CA;开发环境可以显式使用 `sslMode: disable`。
|
||
- 每个 PostgreSQLInstance 在其管理 database 中维护 controller 专用 registry schema。
|
||
registry 是受管资源所有权、安装身份和 Retain 后 unmanaged 标记的权威记录;两个
|
||
CR 的 `status.phase` 是 controller 状态机的权威 checkpoint,Instance status 不聚合
|
||
Tenant 清单。
|
||
- PostgreSQL database 和 role identifier 必须匹配 `^[a-z][a-z0-9_]{0,62}$`,不支持
|
||
需要双引号的大小写或特殊字符名称。
|
||
- External Secrets Operator 是 v1alpha1 的运行依赖。controller 管理同 namespace
|
||
ExternalSecret,但不直接写明文 Secret;Tenant status 同时输出目标 Secret reference
|
||
和供非 Kubernetes 消费者使用的 OpenBao API URL。
|
||
- PostgreSQLInstance 同时声明 DNS `host` 和 IP `hostaddr`;PostgreSQL server 证书必须
|
||
同时包含对应 DNS SAN 和 IP SAN,消费者自行选择连接目标。
|
||
|
||
## 17. 批准状态
|
||
|
||
具体设计决策和本文整体已于 2026-09-10 获得批准,可以进入 API reference、测试和
|
||
实现阶段。同日确认状态机修订:两个 CR 的 `status.phase` 是 controller 工作流的权威
|
||
checkpoint;PostgreSQL registry 只承担所有权、安装身份和保留状态。
|
||
|
||
## 18. 与当前脚手架的已知差异
|
||
|
||
当前 API skeleton 至少需要以下调整:
|
||
|
||
- 删除 Tenant 自选 OpenBao path 的能力,改由部署级 mount、base path 和 Tenant
|
||
identity 推导,并修正当前包含 `kv/` 前缀的示例;
|
||
- 增加 controller 部署级 OpenBao KV mount 和 TLS 配置;
|
||
- 增加部署级 OpenBao consumer address、ClusterSecretStore 和 KV base path 配置;
|
||
- 删除独立 `ownerRole` 字段,使 login role 成为 database owner;
|
||
- 为 Instance 增加 `hostaddr`,为 Tenant 增加目标 Secret 配置及 Secret/Bao URL 输出
|
||
status;
|
||
- 按已确认的 identifier 合同收紧校验;
|
||
- 增加 PostgreSQL controller registry,记录基于 UID 的所有权、安装身份和保留状态;
|
||
- 修正凭据 type 中遗留的 rotation 注释;
|
||
- 使 Condition、不可变字段和 extension 追加语义具备 API 校验或明确的 reconcile
|
||
结果。
|
||
|
||
这些是规格批准后的实现工作,不属于本规格本身。
|