docs: 分离 Database 资源与 Tenant 申请生命周期
This commit is contained in:
+76
-146
@@ -2,33 +2,34 @@
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | --- |
|
||||
| 状态 | Review |
|
||||
| API group | `database.ayatori.ddupan.top` |
|
||||
| version | `v1alpha1` |
|
||||
| 最后更新 | 2026-09-10 |
|
||||
| 状态 | 三资源模型已批准;新增字段与绑定协议待评审 |
|
||||
| API group/version | `database.ayatori.ddupan.top/v1alpha1` |
|
||||
| 最后更新 | 2026-09-24 |
|
||||
|
||||
本文把已批准的系统规格映射为 CRD 字段合同。API types、生成 CRD、sample 和测试必须与本文
|
||||
一致。Ayatori 尚未注册这些 API,本页是后续实现的规范来源。
|
||||
以 [系统规格](specification.md) 与
|
||||
[ADR-0009](../decisions/0009-database-resource-and-claim.md) 为准。尚未实现新 API,
|
||||
本页不提供可直接 apply 的三资源 YAML,以免把工作名称和未决字段当作已发布合同。
|
||||
|
||||
## 通用约定
|
||||
|
||||
- PostgreSQL identifier 匹配 `^[a-z][a-z0-9_]{0,62}$`。
|
||||
- 所有引用名称使用 Kubernetes DNS label/name 的相应校验。
|
||||
- Tenant 的 `spec.instanceRef` 与 `metadata.name` 长度合计不超过 241 个字符,确保派生的
|
||||
`<instanceRef>-<metadata.name>-postgresql` 不超过 Kubernetes DNS subdomain 的
|
||||
253 字符限制。
|
||||
- port、TLS mode、deletion policy 等固定默认值由 CRD defaulting 提供。database、
|
||||
loginRole、Secret 名称等依赖其他字段的值是 controller 语义默认值:字段保持省略,
|
||||
controller 计算 effective value 并通过 status/受管资源展示,不引入 mutating webhook。
|
||||
- 需要读取旧值或跨字段的校验由 CEL 或 controller 完成。
|
||||
- `status` 由 controller 独占写入,禁止出现密码、Token、管理用户名或完整连接串。
|
||||
- 两个 Kind 都只承诺一个 `Ready` Condition;调用方不得依赖内部协调阶段。
|
||||
- Instance 是 cluster-scoped;Tenant 是 namespaced;Database scope 待字段评审。
|
||||
- 每类资源提供唯一的 Ready Condition、observedGeneration;phase 用于进度展示,
|
||||
不能单独作为写权限或所有权证明。
|
||||
- 引用必须区分定位名称与已绑定 UID;同名新对象不继承绑定。
|
||||
- controller 管理 status;资源侧管理与回收权限不得随 Tenant editor 权限自动授予。
|
||||
- 固定默认值使用 CRD defaulting,跨字段/不可变校验使用 CEL 或 controller;
|
||||
并发更新使用 resourceVersion,不增加 mutating webhook 或跨系统事务。
|
||||
- database/role identifier 继续匹配 `^[a-z][a-z0-9_]{0,62}$`。
|
||||
- 动态申请的 database/loginRole 省略时继续以 Tenant metadata.name 为语义默认值;
|
||||
显式导入资源使用实际目标,不从新 Tenant 名称重新推导。最终互斥字段须经 API 评审。
|
||||
- 投射仍位于 Tenant namespace,ExternalSecret 默认命名沿用
|
||||
`<instanceRef>-<metadata.name>-postgresql`;目标 Secret 可由申请指定,省略时同名。
|
||||
有效 Instance 名称与 Tenant 名称合计不超过 241 字符;完整名称须满足 API 名称校验。
|
||||
- 任何 spec/status/错误不得出现密码、Token 或完整秘密响应。
|
||||
|
||||
## PostgreSQLInstance
|
||||
|
||||
cluster-scoped,short name 为 `pginstance`。
|
||||
|
||||
### Spec
|
||||
保留管理入口字段:
|
||||
|
||||
| JSON path | 类型 | 必填/默认 | 合同 |
|
||||
| --- | --- | --- | --- |
|
||||
@@ -41,150 +42,79 @@ cluster-scoped,short name 为 `pginstance`。
|
||||
| `spec.adminCredentialRef.usernameKey` | string | `username` | Secret data 中的键名 |
|
||||
| `spec.adminCredentialRef.passwordKey` | string | `password` | Secret data 中的键名 |
|
||||
|
||||
`adminCredentialRef` 不接受 namespace 或 Bao path。管理 Secret 固定在 controller
|
||||
namespace,名称须合法,两个字段须存在且非空。管理员维护 ExternalSecret,由 ESO
|
||||
同步;controller 只读管理 Secret,不创建或修改它。此为 2026-09-13 批准的修订,
|
||||
现有 API types、生成 CRD 和 samples 尚未更新。
|
||||
|
||||
Instance endpoint、管理凭据引用可以修改。修改后 controller 重新验证。
|
||||
2026-09-14 修订:v1alpha1 不实现 allowedExtensions;现有 API types、生成 CRD 和
|
||||
samples 中的字段待后续移除,不作为一个可配置但被忽略的策略保留。
|
||||
扩展请求按目标 PostgreSQL 实际可安装列表判断,可用列表由应用层查询。
|
||||
管理 Secret 固定在 controller namespace,不接受 namespace 或 Bao path。
|
||||
管理员维护其 ExternalSecret,controller 只读;有效用户名/密码变化时重验连接。
|
||||
endpoint 变化使旧观察失效,不迁移旧服务器的数据,不自动接管旧 UID 的资源。
|
||||
|
||||
endpoint 由管理员负责,不校验变更前后是否同一物理服务器/registry,只重验新配置
|
||||
的连接与管理能力。新 UID 按新 Instance 处理,不授权接管旧 UID 的 Tenant 资源。
|
||||
Status 保留 observedGeneration、postgresqlVersion 和 conditions;
|
||||
phase 为 Pending、Validating、Ready、Deleting,移除 InitializingRegistry。
|
||||
Ready 要求连接、metadata 与所需管理权限,不要求 registry。
|
||||
实例实际可用扩展来自查询,不提供 allowedExtensions 配置。
|
||||
|
||||
### Status
|
||||
开始受管时保存 finalizer;删除时阻止新供应,并检查 Database(含 Released)及未绑定
|
||||
Tenant 引用。无引用才解除,不级联删除资源;查询失败不能视为无引用。
|
||||
|
||||
| JSON path | 类型 | 含义 |
|
||||
| --- | --- | --- |
|
||||
| `status.observedGeneration` | int64 | 最近完成有结论协调的 generation |
|
||||
| `status.phase` | enum | `Pending`、`Validating`、`InitializingRegistry`、`Ready`、`Deleting` |
|
||||
| `status.postgresqlVersion` | string | 从 server 回读的版本,不用于客户端解析 |
|
||||
| `status.conditions[]` | `metav1.Condition` | 至少包含唯一的 `Ready` |
|
||||
## Database 资源(工作 Kind:PostgreSQLDatabase)
|
||||
|
||||
print columns:`Endpoint=.spec.endpoint.host`、`Phase`、`Ready`、`Age`。
|
||||
以下是字段职责,不是已批准的 JSON schema:
|
||||
|
||||
Instance `Ready=True` 要求管理凭据可读、TLS/认证成功、server metadata 可读、registry
|
||||
可访问且权限预检成功。它不代表数据库已经备份或高可用。
|
||||
| 内容 | 合同 |
|
||||
| --- | --- |
|
||||
| `instanceRef` | Database 自身必填;定位来源并记录绑定的 Instance UID,不依赖 Tenant 补齐 |
|
||||
| 外部目标 | 实际 database 名称及已确认的管理范围;操作开始后不可隐式改目标 |
|
||||
| 来源 | 区分动态供应与管理员显式导入;不能从同名存在推断导入授权 |
|
||||
| 申请预留/绑定 | 至多一个 Tenant,含 namespace/name/UID;Released 保留旧身份 |
|
||||
| 回收策略 | 资源侧 Retain(默认)或显式 Delete;普通 Tenant editor 不得扩大授权 |
|
||||
| 观察与进度 | 实际目标、当前阶段、条件和安全诊断,不保存秘密 |
|
||||
|
||||
管理凭据从 Kubernetes Secret 装配;已有有效凭据可访问 PostgreSQL 时,Bao/ESO
|
||||
暂时不可用不单独撤销 Instance Ready。Tenant 凭据操作仍依赖 Bao。
|
||||
概念生命周期包含供应/验证、可绑定、已绑定、Released 和删除;最终 phase 枚举待协议评审。
|
||||
Released 不自动变成可绑定。Database 不以 Tenant 为 GC owner;使用中的资源受删除保护。
|
||||
|
||||
导入的初始检查只读;存在不等于 Ready,也不等于有权交付给任意 Tenant。
|
||||
默认保留不隐含密码、owner、授权或删除的变更许可。
|
||||
|
||||
## PostgreSQLTenant
|
||||
|
||||
namespaced,short name 为 `pgtenant`。
|
||||
保留用户申请与交付职责:
|
||||
|
||||
### Spec
|
||||
- 动态申请描述 Instance、所需数据库/登录角色、扩展与目标 Secret;
|
||||
或显式引用管理员已登记的 Database。后者从 Database 获取 Instance,不重复指定来源;
|
||||
两条路径互斥,具体 schema 在 API 切片固定。
|
||||
- 绑定前固定有效需求,绑定/开始供应后不能通过改引用或名称迁移资源。
|
||||
- extensions 成功后只允许追加,不自动卸载。
|
||||
- status 展示绑定 Database 身份、Ready、交付 Secret 引用及不含认证信息的 OpenBao URL。
|
||||
- Tenant 删除释放申请,按照 Database 的回收策略处理,不独立持有最终删除授权。
|
||||
|
||||
| JSON path | 类型 | 必填/默认 | 合同 |
|
||||
| --- | --- | --- | --- |
|
||||
| `spec.instanceRef` | string | 必填 | cluster-scoped Instance 名称 |
|
||||
| `spec.database` | string | `metadata.name` | 合法 PostgreSQL identifier |
|
||||
| `spec.loginRole` | string | `metadata.name` | database owner 兼应用 login |
|
||||
| `spec.extensions` | set[string] | 空集合 | 必须属于目标实例实际可安装的扩展列表;成功创建后只允许追加 |
|
||||
| `spec.credential.secretName` | string | `<instance>-<name>-postgresql` | 合法的同 namespace ESO target Secret 名称 |
|
||||
| `spec.deletionPolicy` | enum | `Retain` | `Retain` 或 `Delete` |
|
||||
旧 Tenant `spec.deletionPolicy` 不再作为最终资源回收策略;旧字段表、供应 phase 枚举及
|
||||
仅按 Tenant namespace/name 派生凭据路径的规则不再是实现合同。已有代码没有兼容负担,
|
||||
不保留两套相互覆盖的策略字段。
|
||||
|
||||
Tenant 不声明 OpenBao mount 或 path。controller 使用部署级 mount/base path 和
|
||||
`namespace/name` 推导稳定路径,并用 UID metadata 验证所有权。ExternalSecret 固定为
|
||||
`<instanceRef>-<metadata.name>-postgresql`;目标 Secret 可以由用户指定,只需
|
||||
满足 Kubernetes Secret 名称校验,不限制命名内容;省略时使用相同默认名。
|
||||
## 绑定协议评审要求
|
||||
|
||||
`instanceRef`、`database`、`loginRole` 和 `credential.secretName` 在首次成功创建外部
|
||||
状态后不可变。
|
||||
`extensions` 只允许集合不变或追加;移除返回 `ImmutableField`,不会执行
|
||||
`DROP EXTENSION`。`deletionPolicy` 在对象进入删除前可以修改;删除开始后以 finalizer
|
||||
首次观察到的值为准,避免清理过程中改变授权范围。
|
||||
实现前必须明确:
|
||||
|
||||
### Status
|
||||
1. 资源 scope 与 typed reference 格式,管理员预留/导入与普通申请者的 RBAC 边界。
|
||||
2. 资源侧排他记录的写入点、双向绑定顺序、API 冲突重试与单边完成恢复。
|
||||
3. Tenant UID 变化、对象删除、Released 旧引用与管理员重新授权的判断。
|
||||
4. 同一物理数据库重复登记的冲突处理;列表查询不是原子认领。
|
||||
5. Database 的 role/凭据管理范围、稳定凭据定位、旧访问处置与投射清理顺序。
|
||||
6. 删除开始后的策略固定点和 Tenant/Database finalizer 配合。
|
||||
|
||||
| JSON path | 类型 | 含义 |
|
||||
| --- | --- | --- |
|
||||
| `status.observedGeneration` | int64 | 最近完成有结论协调的 generation |
|
||||
| `status.phase` | enum | controller 状态机的权威 checkpoint |
|
||||
| `status.database` | string | 应用语义默认值后的实际 database 名称 |
|
||||
| `status.loginRole` | string | 应用语义默认值后的实际 owner/login role 名称 |
|
||||
| `status.databaseOID` | uint32 | 回读的 database OID,仅供诊断 |
|
||||
| `status.credential.secretRef.name` | string | 同 namespace 目标 Secret 名称 |
|
||||
| `status.credential.openBaoURL` | string | 完整 KV v2 API URL,不含认证信息 |
|
||||
| `status.conditions[]` | `metav1.Condition` | 至少包含唯一的 `Ready` |
|
||||
该协议使用 Kubernetes API 持久化,不为它新增 PostgreSQL registry。
|
||||
未完成一致绑定不得供应或交付;外部创建结果不确定按 Conflict 人工处理。
|
||||
|
||||
Secret reference 不重复 namespace,因为它必定与 Tenant 同 namespace。OpenBao URL 格式
|
||||
为 `<consumer-address>/v1/<mount>/data/<derived-path>`;不得包含 Token、用户名、密码或
|
||||
query credential。
|
||||
## Conditions
|
||||
|
||||
Tenant phase 枚举为 `Pending`、`Planned`、`CredentialCreated`、`RoleCreated`、
|
||||
`DatabaseCreated`、`ExternalSecretCreated`、`CredentialProjected`、`Ready`、`Deleting`。
|
||||
它不包含 `Failed` 或 `Retained`;失败类型由 Condition Reason 表达。
|
||||
每类资源至少提供唯一的 Ready;同类型 Condition 不重复,维护 lastTransitionTime 与
|
||||
observedGeneration。最低安全错误分类见 [系统规格](specification.md#10-conditions-与可观测性)。
|
||||
|
||||
print columns:`Instance`、`Database`、`Phase`、`Secret`、`Ready`、`Age`。完整 OpenBao URL 只在
|
||||
YAML/JSON status 中输出。
|
||||
Conflict 必须说明目标、步骤、已确认与不确定部分及人工核实建议;不能建议清空 status、
|
||||
伪造 Ready 或改密码绕过。可恢复依赖故障退避重试,冲突不忙循环。
|
||||
具体 Reason 在 API 切片固定,Released 不得误报为可立即交付。
|
||||
|
||||
两个 Kind 的 `status.phase` 都是 controller 状态机的权威 checkpoint。controller 用它
|
||||
选择下一候选动作,但必须在动作前后核对外部事实,不能仅凭 phase 跳过幂等检查。status
|
||||
丢失或领先于实际状态时必须保守重建/纠正。自动化就绪判断仍应读取 `Ready` Condition;
|
||||
phase 用于进度展示、恢复和排障。
|
||||
## 实现验收
|
||||
|
||||
## Condition
|
||||
|
||||
每种类型最多一个 Condition;更新必须保留正确的 `lastTransitionTime` 语义。
|
||||
|
||||
| Reason | Kind | 可重试性 |
|
||||
| --- | --- | --- |
|
||||
| `Reconciling` | 两者 | 正常进行中 |
|
||||
| `Ready` | 两者 | 已收敛 |
|
||||
| `InvalidSpec` | 两者 | 修改 spec 前不会恢复 |
|
||||
| `ImmutableField` | Tenant | 恢复原值或重新迁移 |
|
||||
| `DependencyUnavailable` | 两者 | 自动重试 |
|
||||
| `AuthenticationFailed` | Instance | 修复凭据/TLS 后重试 |
|
||||
| `InsufficientPrivileges` | Instance | 修复管理 role 后重试 |
|
||||
| `InstanceNotReady` | Tenant | Instance 恢复后重试 |
|
||||
| `Conflict` | Tenant | 人工解除名称/所有权冲突 |
|
||||
| `ProvisioningFailed` | Tenant | 按错误类别退避重试 |
|
||||
| `CredentialProjectionFailed` | Tenant | ESO/Secret 恢复后重试 |
|
||||
|
||||
`Ready=True` 必须使用 Reason `Ready`。处理中为 `Unknown/Reconciling`;已知未满足合同为
|
||||
`False`。Condition message 可以包含资源名和错误类别,禁止包含凭据值或完整 Secret。
|
||||
|
||||
## 删除语义
|
||||
|
||||
- `Retain` 不需要等待外部依赖;删除 CR 后外部记录保留原 UID 并标记 unmanaged。
|
||||
- `Delete` 添加 finalizer,严格按规格的所有权验证和清理顺序执行;失败保持 finalizer。
|
||||
- Instance 开始受管时即添加并保存 finalizer;删除时停止新供应,存在 Tenant 引用
|
||||
(包括正在删除的 Tenant)就保留 finalizer,无引用才移除。引用查询失败时继续等待。
|
||||
不级联删除 Tenant 或外部资源;管理员可使用运维逃生流程。
|
||||
- finalizer 不禁止创建 Tenant CR;并发创建者遇到删除中或不存在的 Instance 不得
|
||||
开始供应。首版不增加跨对象锁或准入控制,不承诺跨对象原子删除。
|
||||
|
||||
## 示例
|
||||
|
||||
```yaml
|
||||
apiVersion: database.ayatori.ddupan.top/v1alpha1
|
||||
kind: PostgreSQLInstance
|
||||
metadata:
|
||||
name: shared
|
||||
spec:
|
||||
endpoint:
|
||||
host: postgresql.home.arpa
|
||||
hostaddr: 192.0.2.10
|
||||
port: 5432
|
||||
database: postgres
|
||||
sslMode: verify-full
|
||||
adminCredentialRef:
|
||||
name: shared-postgresql-admin
|
||||
---
|
||||
apiVersion: database.ayatori.ddupan.top/v1alpha1
|
||||
kind: PostgreSQLTenant
|
||||
metadata:
|
||||
name: netbox
|
||||
namespace: netbox
|
||||
spec:
|
||||
instanceRef: shared
|
||||
database: netbox
|
||||
loginRole: netbox
|
||||
extensions: [pg_trgm]
|
||||
credential:
|
||||
secretName: shared-netbox-database-credentials
|
||||
deletionPolicy: Retain
|
||||
```
|
||||
CRD defaulting、CEL、status subresource、resourceVersion、并发绑定、重启与依赖 watch
|
||||
使用真实 API server 验证;ownerReference/namespace 删除的实际 GC 使用测试集群。
|
||||
类型、CRD、sample 和 contract tests 必须一起对齐,不以 fake client 替代 API 语义。
|
||||
|
||||
Reference in New Issue
Block a user